-
Notifications
You must be signed in to change notification settings - Fork 26
StringViewArray: enable nullable reference types and optimize encoding with stackalloc/ArrayPool #400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
StringViewArray: enable nullable reference types and optimize encoding with stackalloc/ArrayPool #400
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,10 @@ | |||||
| // See the License for the specific language governing permissions and | ||||||
| // limitations under the License. | ||||||
|
|
||||||
| #nullable enable | ||||||
|
|
||||||
| using System; | ||||||
| using System.Buffers; | ||||||
| using System.Collections; | ||||||
| using System.Collections.Generic; | ||||||
| using System.Runtime.InteropServices; | ||||||
|
|
@@ -22,33 +25,63 @@ | |||||
|
|
||||||
| namespace Apache.Arrow | ||||||
| { | ||||||
| public class StringViewArray : BinaryViewArray, IReadOnlyList<string> | ||||||
| public class StringViewArray(ArrayData data) : BinaryViewArray(ArrowTypeId.StringView, data), IReadOnlyList<string?> | ||||||
| { | ||||||
| public static readonly Encoding DefaultEncoding = Encoding.UTF8; | ||||||
| public static Encoding DefaultEncoding { get; } = new UTF8Encoding(false); | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little concerned that this could be a breaking behaviour change, but I checked that decoding is forgiving of whether or not a BOM is present, regardless of the I notice that we disable testing the arrow-dotnet/test/Apache.Arrow.Tests/CDataInterfacePythonTests.cs Lines 772 to 773 in dc2c566
Did you find that emitting the BOM was causing a problem when integrating with another Arrow implementation?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @adamreeve Yes, BOM causes problems with DataFusion. |
||||||
|
|
||||||
| public new class Builder : BuilderBase<StringViewArray, Builder> | ||||||
| public new class Builder() : BuilderBase<StringViewArray, Builder>(StringViewType.Default) | ||||||
| { | ||||||
| public Builder() : base(StringViewType.Default) { } | ||||||
|
|
||||||
| protected override StringViewArray Build(ArrayData data) | ||||||
| { | ||||||
| return new StringViewArray(data); | ||||||
| } | ||||||
|
|
||||||
| public Builder Append(string value, Encoding encoding = null) | ||||||
| public Builder Append(string? value, Encoding? encoding = null) | ||||||
| { | ||||||
| if (value == null) | ||||||
| if (value is null) | ||||||
| { | ||||||
| return AppendNull(); | ||||||
| } | ||||||
| encoding = encoding ?? DefaultEncoding; | ||||||
| byte[] span = encoding.GetBytes(value); | ||||||
| return Append(span.AsSpan()); | ||||||
|
|
||||||
| encoding ??= DefaultEncoding; | ||||||
| int maxByteCount = encoding.GetMaxByteCount(value.Length); | ||||||
| #if NETCOREAPP | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please put preprocessor directives in flush-left like the rest of the codebase. |
||||||
| byte[]? buffer = null; | ||||||
|
|
||||||
| Span<byte> span = maxByteCount <= 1024 | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've been using 256 as a conservative upper bound for stackalloc in libraries; see |
||||||
| ? stackalloc byte[maxByteCount] | ||||||
| : buffer = ArrayPool<byte>.Shared.Rent(maxByteCount); | ||||||
|
|
||||||
| try | ||||||
| { | ||||||
| int encodeBytes = encoding.GetBytes(value, span); | ||||||
| return Append(span.Slice(0, encodeBytes)); | ||||||
| } | ||||||
| finally | ||||||
| { | ||||||
| if (buffer is not null) | ||||||
| { | ||||||
| ArrayPool<byte>.Shared.Return(buffer); | ||||||
| } | ||||||
| } | ||||||
| #else | ||||||
| byte[] buffer = ArrayPool<byte>.Shared.Rent(maxByteCount); | ||||||
|
|
||||||
| try | ||||||
| { | ||||||
| int encodeBytes = encoding.GetBytes(value, 0, value.Length, buffer, 0); | ||||||
| return Append(buffer.AsSpan(0, encodeBytes)); | ||||||
| } | ||||||
| finally | ||||||
| { | ||||||
| ArrayPool<byte>.Shared.Return(buffer); | ||||||
| } | ||||||
| #endif | ||||||
| } | ||||||
|
|
||||||
| public Builder AppendRange(IEnumerable<string> values, Encoding encoding = null) | ||||||
| public Builder AppendRange(IEnumerable<string?> values, Encoding? encoding = null) | ||||||
| { | ||||||
| foreach (string value in values) | ||||||
| foreach (string? value in values) | ||||||
| { | ||||||
| Append(value, encoding); | ||||||
| } | ||||||
|
|
@@ -57,31 +90,41 @@ public Builder AppendRange(IEnumerable<string> values, Encoding encoding = null) | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| public StringViewArray(ArrayData data) | ||||||
| : base(ArrowTypeId.StringView, data) { } | ||||||
|
|
||||||
| public StringViewArray(int length, | ||||||
| public StringViewArray | ||||||
| ( | ||||||
| int length, | ||||||
| ArrowBuffer valueOffsetsBuffer, | ||||||
| ArrowBuffer dataBuffer, | ||||||
| ArrowBuffer nullBitmapBuffer, | ||||||
| int nullCount = 0, int offset = 0) | ||||||
| : this(new ArrayData(StringViewType.Default, length, nullCount, offset, | ||||||
| new[] { nullBitmapBuffer, valueOffsetsBuffer, dataBuffer })) | ||||||
| { } | ||||||
| int nullCount = 0, | ||||||
| int offset = 0 | ||||||
| ) : this | ||||||
| ( | ||||||
| new ArrayData | ||||||
| ( | ||||||
| StringViewType.Default, | ||||||
| length, | ||||||
| nullCount, | ||||||
| offset, | ||||||
| [nullBitmapBuffer, valueOffsetsBuffer, dataBuffer] | ||||||
| ) | ||||||
| ) | ||||||
| { | ||||||
| } | ||||||
|
|
||||||
| public override void Accept(IArrowArrayVisitor visitor) => Accept(this, visitor); | ||||||
|
|
||||||
| public string GetString(int index, Encoding encoding = default) | ||||||
| public string? GetString(int index, Encoding? encoding = null) | ||||||
| { | ||||||
| encoding ??= DefaultEncoding; | ||||||
|
|
||||||
| ReadOnlySpan<byte> bytes = GetBytes(index, out bool isNull); | ||||||
|
|
||||||
| if (isNull) | ||||||
| { | ||||||
| return null; | ||||||
| } | ||||||
| if (bytes.Length == 0) | ||||||
|
|
||||||
| if (bytes.Length is 0) | ||||||
| { | ||||||
| return string.Empty; | ||||||
| } | ||||||
|
|
@@ -93,11 +136,11 @@ public string GetString(int index, Encoding encoding = default) | |||||
| } | ||||||
| } | ||||||
|
|
||||||
| int IReadOnlyCollection<string>.Count => Length; | ||||||
| int IReadOnlyCollection<string?>.Count => Length; | ||||||
|
|
||||||
| string IReadOnlyList<string>.this[int index] => GetString(index); | ||||||
| string? IReadOnlyList<string?>.this[int index] => GetString(index); | ||||||
|
|
||||||
| IEnumerator<string> IEnumerable<string>.GetEnumerator() | ||||||
| IEnumerator<string?> IEnumerable<string?>.GetEnumerator() | ||||||
| { | ||||||
| for (int index = 0; index < Length; index++) | ||||||
| { | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a codebase is easier to read when it's consistent in its use of syntax and personally found this new form jarring. I'm curious what other people think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also found it a bit jarring but I haven't been doing as much work in C# lately so maybe I'm just not used to this. I noticed Rider always suggests refactoring to this form.