-
Notifications
You must be signed in to change notification settings - Fork 357
Open
Labels
Description
Is your feature request related to a problem? Please describe.
We currently allocation strings when parsing common property names
Describe the solution you'd like
Eliminate avoidable string allocations in JsonReader when parsing common property names (e.g., "@odata.context"/"@context", "@odata.type"/"@type", "@odata.count"/"@count", "@odata.id"/"@id", "value" - maybe even "id"/"Id", "name"/"Name")
private const string IdCamelCasePropertyName = "id";
private const string IdPascalCasePropertyName = "Id";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool Is(ReadOnlySpan<char> s, ReadOnlySpan<char> expect) =>
s.Length == expect.Length && s.SequenceEqual(expect);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryInternCommonPropertyName(ReadOnlySpan<char> span, out string value)
{
switch (span.Length)
{
case 2: // id / Id
if (span[0] == 'i' && span[1] == 'd') { value = IdCamelCasePropertyName; return true; }
if (span[0] == 'I' && span[1] == 'd') { value = IdPascalCasePropertyName; return true; }
break;
case 3: // @id
if (Is(span, SimplifiedODataIdPropertyName)) { value = SimplifiedODataIdPropertyName; return true; }
break;
case 5: // value, @type
if (Is(span, ODataValuePropertyName)) { value = ODataValuePropertyName; return true; }
if (Is(span, SimplifiedODataTypePropertyName)) { value = SimplifiedODataTypePropertyName; return true; }
break;
case 8: // @context
if (Is(span, SimplifiedODataContextPropertyName)) { value = SimplifiedODataContextPropertyName; return true; }
break;
case 9: // @odata.id
if (Is(span, PrefixedODataIdPropertyName)) { value = PrefixedODataIdPropertyName; return true; }
break;
case 11: // @odata.type
if (Is(span, PrefixedODataTypePropertyName)) { value = PrefixedODataTypePropertyName; return true; }
break;
case 14: // @odata.context
if (Is(span, PrefixedODataContextPropertyName)) { value = PrefixedODataContextPropertyName; return true; }
break;
// ... other common property names
}
value = null;
return false;
}
Then in ParseName
:
private string ParseName()
{
Debug.Assert(this.tokenStartIndex < this.storedCharacterCount, "Must have at least one character available.");
char firstCharacter = this.characterBuffer[this.tokenStartIndex];
if (firstCharacter == '"' || firstCharacter == '\'')
{
return this.ParseStringPrimitiveValue();
}
int currentCharacterTokenRelativeIndex = 0;
do
{
char c = this.characterBuffer[this.tokenStartIndex + currentCharacterTokenRelativeIndex];
if (IsCharacterAllowedInPropertyName(c))
{
currentCharacterTokenRelativeIndex++;
}
else
{
break;
}
}
while ((this.tokenStartIndex + currentCharacterTokenRelativeIndex) < this.storedCharacterCount || this.ReadInput());
ReadOnlySpan<char> span = this.characterBuffer.AsSpan(this.tokenStartIndex, currentCharacterTokenRelativeIndex);
if (TryInternCommonPropertyName(span, out string interned))
{
this.tokenStartIndex += currentCharacterTokenRelativeIndex;
return interned; // return shared instance (no allocation)
}
return this.ConsumeTokenToString(currentCharacterTokenRelativeIndex);
}
NOTE: To ensure this change delivers measurable improvements, benchmarking is essential to validate its impact on allocations and runtime performance.