Skip to content

Commit 66634f8

Browse files
authored
Reduce allocations in the ImageElementConverter and ImageIdConverter Read methods (#78881)
* Reduce allocations in the ImageElementConverter and ImageIdConverter Read methods These methods show up in the typing scenario in the razor speedometer test as about 0.9% (63 MB) of allocations. 1) Changed ImageIdConverter to be more like ImageElementConverter and not create a JsonDocument object to query 2) Changed several Utf8JsonReader.GetText calls to instead use Utf8JsonReader.CopyString 3) Changed JsonElement.GetString and new Guid(...) to instead use Utf8JsonReader.GetGuid() Note that if this PR is merged, I'll also try to make a change to vslanguageserverclient to also do the same as that code has the same issues.
1 parent a38d156 commit 66634f8

File tree

2 files changed

+61
-15
lines changed

2 files changed

+61
-15
lines changed

src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageElementConverter.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Roslyn.Text.Adornments;
1010

1111
namespace Roslyn.LanguageServer.Protocol;
12+
1213
internal sealed class ImageElementConverter : JsonConverter<ImageElement>
1314
{
1415
public static readonly ImageElementConverter Instance = new();
@@ -20,6 +21,8 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert,
2021
ImageId? imageId = null;
2122
string? automationName = null;
2223

24+
Span<char> scratchChars = stackalloc char[64];
25+
2326
while (reader.Read())
2427
{
2528
if (reader.TokenType == JsonTokenType.EndObject)
@@ -30,7 +33,11 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert,
3033

3134
if (reader.TokenType == JsonTokenType.PropertyName)
3235
{
33-
var propertyName = reader.GetString();
36+
var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length;
37+
38+
var propertyNameLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
39+
var propertyName = propertyNameLength >= 0 ? scratchChars[..propertyNameLength] : reader.GetString().AsSpan();
40+
3441
reader.Read();
3542
switch (propertyName)
3643
{
@@ -41,7 +48,10 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert,
4148
automationName = reader.GetString();
4249
break;
4350
case ObjectContentConverter.TypeProperty:
44-
if (reader.GetString() != nameof(ImageElement))
51+
var typePropertyLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
52+
var typeProperty = typePropertyLength >= 0 ? scratchChars[..typePropertyLength] : reader.GetString().AsSpan();
53+
54+
if (!typeProperty.SequenceEqual(nameof(ImageElement).AsSpan()))
4555
throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageElement)}");
4656
break;
4757
default:
@@ -60,7 +70,10 @@ public override void Write(Utf8JsonWriter writer, ImageElement value, JsonSerial
6070
writer.WriteStartObject();
6171
writer.WritePropertyName(nameof(ImageElement.ImageId));
6272
ImageIdConverter.Instance.Write(writer, value.ImageId, options);
63-
writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName);
73+
74+
if (value.AutomationName != null)
75+
writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName);
76+
6477
writer.WriteString(ObjectContentConverter.TypeProperty, nameof(ImageElement));
6578
writer.WriteEndObject();
6679
}

src/LanguageServer/Protocol/Protocol/Internal/Converters/ImageIdConverter.cs

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using Roslyn.Core.Imaging;
99

1010
namespace Roslyn.LanguageServer.Protocol;
11+
1112
internal sealed class ImageIdConverter : JsonConverter<ImageId>
1213
{
1314
public static readonly ImageIdConverter Instance = new();
@@ -16,21 +17,53 @@ public override ImageId Read(ref Utf8JsonReader reader, Type objectType, JsonSer
1617
{
1718
if (reader.TokenType == JsonTokenType.StartObject)
1819
{
19-
using var document = JsonDocument.ParseValue(ref reader);
20-
var root = document.RootElement;
21-
if (root.TryGetProperty(ObjectContentConverter.TypeProperty, out var typeProperty) && typeProperty.GetString() != nameof(ImageId))
20+
Guid? guid = null;
21+
int? id = null;
22+
23+
Span<char> scratchChars = stackalloc char[64];
24+
25+
while (reader.Read())
2226
{
23-
throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}");
24-
}
27+
if (reader.TokenType == JsonTokenType.EndObject)
28+
{
29+
if (guid is null || id is null)
30+
throw new JsonException("Expected properties Guid and Id to be present");
2531

26-
var guid = root.GetProperty(nameof(ImageId.Guid)).GetString() ?? throw new JsonException();
27-
var id = root.GetProperty(nameof(ImageId.Id)).GetInt32();
28-
return new ImageId(new Guid(guid), id);
29-
}
30-
else
31-
{
32-
throw new JsonException("Expected start object or null tokens");
32+
return new ImageId(guid.Value, id.Value);
33+
}
34+
35+
if (reader.TokenType == JsonTokenType.PropertyName)
36+
{
37+
var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length;
38+
39+
var propertyNameLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
40+
var propertyName = propertyNameLength >= 0 ? scratchChars[..propertyNameLength] : reader.GetString().AsSpan();
41+
42+
reader.Read();
43+
switch (propertyName)
44+
{
45+
case nameof(ImageId.Guid):
46+
guid = reader.GetGuid();
47+
break;
48+
case nameof(ImageId.Id):
49+
id = reader.GetInt32();
50+
break;
51+
case ObjectContentConverter.TypeProperty:
52+
var typePropertyLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
53+
var typeProperty = typePropertyLength >= 0 ? scratchChars[..typePropertyLength] : reader.GetString().AsSpan();
54+
55+
if (!typeProperty.SequenceEqual(nameof(ImageId).AsSpan()))
56+
throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}");
57+
break;
58+
default:
59+
reader.Skip();
60+
break;
61+
}
62+
}
63+
}
3364
}
65+
66+
throw new JsonException("Expected start object or null tokens");
3467
}
3568

3669
public override void Write(Utf8JsonWriter writer, ImageId value, JsonSerializerOptions options)

0 commit comments

Comments
 (0)