Skip to content

Commit

Permalink
System.Text.Json: stackalloc constants + misc PR feedback (#55350)
Browse files Browse the repository at this point in the history
* Follow-up feedback from #54186.

* Code review.

* Code review.

* Dedicated constant for the max number of chars permitted to be allocated on the stack.
  • Loading branch information
CodeBlanch committed Jul 18, 2021
1 parent c5edf0e commit f2a55e2
Show file tree
Hide file tree
Showing 33 changed files with 169 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ internal bool TryGetNamedPropertyValue(int index, ReadOnlySpan<char> propertyNam
int startIndex = index + DbRow.Size;
int endIndex = checked(row.NumberOfRows * DbRow.Size + index);

if (maxBytes < JsonConstants.StackallocThreshold)
if (maxBytes < JsonConstants.StackallocByteThreshold)
{
Span<byte> utf8Name = stackalloc byte[JsonConstants.StackallocThreshold];
Span<byte> utf8Name = stackalloc byte[JsonConstants.StackallocByteThreshold];
int len = JsonReaderHelper.GetUtf8FromText(propertyName, utf8Name);
utf8Name = utf8Name.Slice(0, len);

Expand Down Expand Up @@ -139,7 +139,7 @@ internal bool TryGetNamedPropertyValue(int index, ReadOnlySpan<byte> propertyNam
out JsonElement value)
{
ReadOnlySpan<byte> documentSpan = _utf8Json.Span;
Span<byte> utf8UnescapedStack = stackalloc byte[JsonConstants.StackallocThreshold];
Span<byte> utf8UnescapedStack = stackalloc byte[JsonConstants.StackallocByteThreshold];

// Move to the row before the EndObject
int index = endIndex - DbRow.Size;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,8 +298,8 @@ internal bool TextEquals(int index, ReadOnlySpan<char> otherText, bool isPropert
byte[]? otherUtf8TextArray = null;

int length = checked(otherText.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);
Span<byte> otherUtf8Text = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[JsonConstants.StackallocThreshold] :
Span<byte> otherUtf8Text = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(otherUtf8TextArray = ArrayPool<byte>.Shared.Rent(length));

ReadOnlySpan<byte> utf16Text = MemoryMarshal.AsBytes(otherText);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ internal static class JsonConstants
public const int MaxWriterDepth = 1_000;
public const int RemoveFlagsBitMask = 0x7FFFFFFF;

public const int StackallocThreshold = 256;
public const int StackallocByteThreshold = 256;
public const int StackallocCharThreshold = StackallocByteThreshold / 2;

// In the worst case, an ASCII character represented as a single utf-8 byte could expand 6x when escaped.
// For example: '+' becomes '\u0043'
Expand All @@ -60,7 +61,7 @@ internal static class JsonConstants
public const int MaxExpansionFactorWhileEscaping = 6;

// In the worst case, a single UTF-16 character could be expanded to 3 UTF-8 bytes.
// Only surrogate pairs expand to 4 UTF-8 bytes but that is a transformation of 2 UTF-16 characters goign to 4 UTF-8 bytes (factor of 2).
// Only surrogate pairs expand to 4 UTF-8 bytes but that is a transformation of 2 UTF-16 characters going to 4 UTF-8 bytes (factor of 2).
// All other UTF-16 characters can be represented by either 1 or 2 UTF-8 bytes.
public const int MaxExpansionFactorWhileTranscoding = 3;

Expand Down Expand Up @@ -95,6 +96,8 @@ internal static class JsonConstants
public const int MinimumDateTimeParseLength = 10; // YYYY-MM-DD
public const int MaximumEscapedDateTimeOffsetParseLength = MaxExpansionFactorWhileEscaping * MaximumDateTimeOffsetParseLength;

public const int MaximumLiteralLength = 5; // Must be able to fit null, true, & false.

// Encoding Helpers
public const char HighSurrogateStart = '\ud800';
public const char HighSurrogateEnd = '\udbff';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public static bool TryParseAsISO(ReadOnlySpan<char> source, out DateTime value)

int maxLength = checked(source.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);

Span<byte> bytes = maxLength <= JsonConstants.StackallocThreshold
? stackalloc byte[JsonConstants.StackallocThreshold]
Span<byte> bytes = maxLength <= JsonConstants.StackallocByteThreshold
? stackalloc byte[JsonConstants.StackallocByteThreshold]
: new byte[maxLength];

int length = JsonReaderHelper.GetUtf8FromText(source, bytes);
Expand Down Expand Up @@ -86,8 +86,8 @@ public static bool TryParseAsISO(ReadOnlySpan<char> source, out DateTimeOffset v

int maxLength = checked(source.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);

Span<byte> bytes = maxLength <= JsonConstants.StackallocThreshold
? stackalloc byte[JsonConstants.StackallocThreshold]
Span<byte> bytes = maxLength <= JsonConstants.StackallocByteThreshold
? stackalloc byte[JsonConstants.StackallocByteThreshold]
: new byte[maxLength];

int length = JsonReaderHelper.GetUtf8FromText(source, bytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan<byte> utf8Value,

int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);

Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(valueArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
Expand All @@ -65,8 +65,8 @@ public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan<byte> utf8Value,

int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);

Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> escapedValue = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(valueArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public static bool TryGetUnescapedBase64Bytes(ReadOnlySpan<byte> utf8Source, int
{
byte[]? unescapedArray = null;

Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocThreshold ?
stackalloc byte[utf8Source.Length] :
Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(unescapedArray = ArrayPool<byte>.Shared.Rent(utf8Source.Length));

Unescape(utf8Source, utf8Unescaped, idx, out int written);
Expand Down Expand Up @@ -44,8 +44,8 @@ public static string GetUnescapedString(ReadOnlySpan<byte> utf8Source, int idx)
int length = utf8Source.Length;
byte[]? pooledName = null;

Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(pooledName = ArrayPool<byte>.Shared.Rent(length));

Unescape(utf8Source, utf8Unescaped, idx, out int written);
Expand All @@ -71,8 +71,8 @@ public static ReadOnlySpan<byte> GetUnescapedSpan(ReadOnlySpan<byte> utf8Source,
int length = utf8Source.Length;
byte[]? pooledName = null;

Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(pooledName = ArrayPool<byte>.Shared.Rent(length));

Unescape(utf8Source, utf8Unescaped, idx, out int written);
Expand All @@ -96,8 +96,8 @@ public static bool UnescapeAndCompare(ReadOnlySpan<byte> utf8Source, ReadOnlySpa

byte[]? unescapedArray = null;

Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocThreshold ?
stackalloc byte[utf8Source.Length] :
Span<byte> utf8Unescaped = utf8Source.Length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(unescapedArray = ArrayPool<byte>.Shared.Rent(utf8Source.Length));

Unescape(utf8Source, utf8Unescaped, 0, out int written);
Expand Down Expand Up @@ -127,12 +127,12 @@ public static bool UnescapeAndCompare(ReadOnlySequence<byte> utf8Source, ReadOnl

int length = checked((int)utf8Source.Length);

Span<byte> utf8Unescaped = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> utf8Unescaped = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(unescapedArray = ArrayPool<byte>.Shared.Rent(length));

Span<byte> utf8Escaped = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> utf8Escaped = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(escapedArray = ArrayPool<byte>.Shared.Rent(length));

utf8Source.CopyTo(utf8Escaped);
Expand Down Expand Up @@ -174,8 +174,8 @@ public static bool TryDecodeBase64(ReadOnlySpan<byte> utf8Unescaped, [NotNullWhe
{
byte[]? pooledArray = null;

Span<byte> byteSpan = utf8Unescaped.Length <= JsonConstants.StackallocThreshold ?
stackalloc byte[utf8Unescaped.Length] :
Span<byte> byteSpan = utf8Unescaped.Length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(pooledArray = ArrayPool<byte>.Shared.Rent(utf8Unescaped.Length));

OperationStatus status = Base64.DecodeFromUtf8(utf8Unescaped, byteSpan, out int bytesConsumed, out int bytesWritten);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public static bool TryGetEscapedDateTime(ReadOnlySpan<byte> source, out DateTime
Debug.Assert(backslash != -1);

Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
Span<byte> sourceUnescaped = stackalloc byte[source.Length];
Span<byte> sourceUnescaped = stackalloc byte[JsonConstants.MaximumEscapedDateTimeOffsetParseLength];

Unescape(source, sourceUnescaped, backslash, out int written);
Debug.Assert(written > 0);
Expand All @@ -277,7 +277,7 @@ public static bool TryGetEscapedDateTimeOffset(ReadOnlySpan<byte> source, out Da
Debug.Assert(backslash != -1);

Debug.Assert(source.Length <= JsonConstants.MaximumEscapedDateTimeOffsetParseLength);
Span<byte> sourceUnescaped = stackalloc byte[source.Length];
Span<byte> sourceUnescaped = stackalloc byte[JsonConstants.MaximumEscapedDateTimeOffsetParseLength];

Unescape(source, sourceUnescaped, backslash, out int written);
Debug.Assert(written > 0);
Expand All @@ -303,7 +303,7 @@ public static bool TryGetEscapedGuid(ReadOnlySpan<byte> source, out Guid value)
int idx = source.IndexOf(JsonConstants.BackSlash);
Debug.Assert(idx != -1);

Span<byte> utf8Unescaped = stackalloc byte[source.Length];
Span<byte> utf8Unescaped = stackalloc byte[JsonConstants.MaximumEscapedGuidLength];

Unescape(source, utf8Unescaped, idx, out int written);
Debug.Assert(written > 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -540,9 +540,9 @@ private bool ConsumeLiteralMultiSegment(ReadOnlySpan<byte> literal, JsonTokenTyp

private bool CheckLiteralMultiSegment(ReadOnlySpan<byte> span, ReadOnlySpan<byte> literal, out int consumed)
{
Debug.Assert(span.Length > 0 && span[0] == literal[0]);
Debug.Assert(span.Length > 0 && span[0] == literal[0] && literal.Length <= JsonConstants.MaximumLiteralLength);

Span<byte> readSoFar = stackalloc byte[literal.Length];
Span<byte> readSoFar = stackalloc byte[JsonConstants.MaximumLiteralLength];
int written = 0;

long prevTotalConsumed = _totalConsumed;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ public bool ValueTextEquals(ReadOnlySpan<char> text)

int length = checked(text.Length * JsonConstants.MaxExpansionFactorWhileTranscoding);

if (length > JsonConstants.StackallocThreshold)
if (length > JsonConstants.StackallocByteThreshold)
{
otherUtf8TextArray = ArrayPool<byte>.Shared.Rent(length);
otherUtf8Text = otherUtf8TextArray;
Expand All @@ -523,8 +523,8 @@ public bool ValueTextEquals(ReadOnlySpan<char> text)
// Cannot create a span directly since it gets passed to instance methods on a ref struct.
unsafe
{
byte* ptr = stackalloc byte[JsonConstants.StackallocThreshold];
otherUtf8Text = new Span<byte>(ptr, JsonConstants.StackallocThreshold);
byte* ptr = stackalloc byte[JsonConstants.StackallocByteThreshold];
otherUtf8Text = new Span<byte>(ptr, JsonConstants.StackallocByteThreshold);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, Jso
int backslash = source.IndexOf(JsonConstants.BackSlash);
Debug.Assert(backslash != -1);

Span<byte> sourceUnescaped = stackalloc byte[source.Length];
Span<byte> sourceUnescaped = stackalloc byte[MaximumEscapedTimeSpanFormatLength];

JsonReaderHelper.Unescape(source, sourceUnescaped, backslash, out int written);
Debug.Assert(written > 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,8 @@ private void WriteBase64EscapeProperty(ReadOnlySpan<char> propertyName, ReadOnly

int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);

Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc char[length] :
Span<char> escapedPropertyName = length <= JsonConstants.StackallocCharThreshold ?
stackalloc char[JsonConstants.StackallocCharThreshold] :
(propertyArray = ArrayPool<char>.Shared.Rent(length));

JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand All @@ -162,8 +162,8 @@ private void WriteBase64EscapeProperty(ReadOnlySpan<byte> utf8PropertyName, Read

int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);

Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> escapedPropertyName = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(propertyArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ private void WriteStringEscapeProperty(ReadOnlySpan<char> propertyName, DateTime

int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);

Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc char[length] :
Span<char> escapedPropertyName = length <= JsonConstants.StackallocCharThreshold ?
stackalloc char[JsonConstants.StackallocCharThreshold] :
(propertyArray = ArrayPool<char>.Shared.Rent(length));

JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand All @@ -167,8 +167,8 @@ private void WriteStringEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, Date

int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);

Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> escapedPropertyName = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(propertyArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ private void WriteStringEscapeProperty(ReadOnlySpan<char> propertyName, DateTime

int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);

Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc char[length] :
Span<char> escapedPropertyName = length <= JsonConstants.StackallocCharThreshold ?
stackalloc char[JsonConstants.StackallocCharThreshold] :
(propertyArray = ArrayPool<char>.Shared.Rent(length));

JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand All @@ -166,8 +166,8 @@ private void WriteStringEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, Date

int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);

Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> escapedPropertyName = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(propertyArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,8 @@ private void WriteNumberEscapeProperty(ReadOnlySpan<char> propertyName, decimal

int length = JsonWriterHelper.GetMaxEscapedLength(propertyName.Length, firstEscapeIndexProp);

Span<char> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc char[length] :
Span<char> escapedPropertyName = length <= JsonConstants.StackallocCharThreshold ?
stackalloc char[JsonConstants.StackallocCharThreshold] :
(propertyArray = ArrayPool<char>.Shared.Rent(length));

JsonWriterHelper.EscapeString(propertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand All @@ -166,8 +166,8 @@ private void WriteNumberEscapeProperty(ReadOnlySpan<byte> utf8PropertyName, deci

int length = JsonWriterHelper.GetMaxEscapedLength(utf8PropertyName.Length, firstEscapeIndexProp);

Span<byte> escapedPropertyName = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
Span<byte> escapedPropertyName = length <= JsonConstants.StackallocByteThreshold ?
stackalloc byte[JsonConstants.StackallocByteThreshold] :
(propertyArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8PropertyName, escapedPropertyName, firstEscapeIndexProp, _options.Encoder, out int written);
Expand Down
Loading

0 comments on commit f2a55e2

Please sign in to comment.