Skip to content

Reduce allocations in the ImageElementConverter and ImageIdConverter Read methods #78881

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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Roslyn.Text.Adornments;

namespace Roslyn.LanguageServer.Protocol;

internal sealed class ImageElementConverter : JsonConverter<ImageElement>
{
public static readonly ImageElementConverter Instance = new();
Expand All @@ -20,6 +21,8 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert,
ImageId? imageId = null;
string? automationName = null;

Span<char> scratchChars = stackalloc char[64];

while (reader.Read())
{
if (reader.TokenType == JsonTokenType.EndObject)
Expand All @@ -30,7 +33,11 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert,

if (reader.TokenType == JsonTokenType.PropertyName)
{
var propertyName = reader.GetString();
var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length;

var propertyNameLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

talked with Todd offline and sounds like we can't do have a helper to or if/else here, so fine with this.

var propertyName = propertyNameLength >= 0 ? scratchChars[..propertyNameLength] : reader.GetString().AsSpan();

reader.Read();
switch (propertyName)
{
Expand All @@ -41,7 +48,10 @@ public override ImageElement Read(ref Utf8JsonReader reader, Type typeToConvert,
automationName = reader.GetString();
break;
case ObjectContentConverter.TypeProperty:
if (reader.GetString() != nameof(ImageElement))
var typePropertyLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
var typeProperty = typePropertyLength >= 0 ? scratchChars[..typePropertyLength] : reader.GetString().AsSpan();

if (!typeProperty.SequenceEqual(nameof(ImageElement).AsSpan()))
throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageElement)}");
break;
default:
Expand All @@ -60,7 +70,10 @@ public override void Write(Utf8JsonWriter writer, ImageElement value, JsonSerial
writer.WriteStartObject();
writer.WritePropertyName(nameof(ImageElement.ImageId));
ImageIdConverter.Instance.Write(writer, value.ImageId, options);
writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName);

if (value.AutomationName != null)
writer.WriteString(nameof(ImageElement.AutomationName), value.AutomationName);

writer.WriteString(ObjectContentConverter.TypeProperty, nameof(ImageElement));
writer.WriteEndObject();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Roslyn.Core.Imaging;

namespace Roslyn.LanguageServer.Protocol;

internal sealed class ImageIdConverter : JsonConverter<ImageId>
{
public static readonly ImageIdConverter Instance = new();
Expand All @@ -16,21 +17,53 @@ public override ImageId Read(ref Utf8JsonReader reader, Type objectType, JsonSer
{
if (reader.TokenType == JsonTokenType.StartObject)
{
using var document = JsonDocument.ParseValue(ref reader);
var root = document.RootElement;
if (root.TryGetProperty(ObjectContentConverter.TypeProperty, out var typeProperty) && typeProperty.GetString() != nameof(ImageId))
Guid? guid = null;
int? id = null;

Span<char> scratchChars = stackalloc char[64];

while (reader.Read())
{
throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}");
}
if (reader.TokenType == JsonTokenType.EndObject)
{
if (guid is null || id is null)
throw new JsonException("Expected properties Guid and Id to be present");

var guid = root.GetProperty(nameof(ImageId.Guid)).GetString() ?? throw new JsonException();
var id = root.GetProperty(nameof(ImageId.Id)).GetInt32();
return new ImageId(new Guid(guid), id);
}
else
{
throw new JsonException("Expected start object or null tokens");
return new ImageId(guid.Value, id.Value);
}

if (reader.TokenType == JsonTokenType.PropertyName)
{
var valueLength = reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length;

var propertyNameLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
var propertyName = propertyNameLength >= 0 ? scratchChars[..propertyNameLength] : reader.GetString().AsSpan();

reader.Read();
switch (propertyName)
{
case nameof(ImageId.Guid):
guid = reader.GetGuid();
break;
case nameof(ImageId.Id):
id = reader.GetInt32();
break;
case ObjectContentConverter.TypeProperty:
var typePropertyLength = valueLength <= scratchChars.Length ? reader.CopyString(scratchChars) : -1;
var typeProperty = typePropertyLength >= 0 ? scratchChars[..typePropertyLength] : reader.GetString().AsSpan();

if (!typeProperty.SequenceEqual(nameof(ImageId).AsSpan()))
throw new JsonException($"Expected {ObjectContentConverter.TypeProperty} property value {nameof(ImageId)}");
break;
default:
reader.Skip();
break;
}
}
}
}

throw new JsonException("Expected start object or null tokens");
}

public override void Write(Utf8JsonWriter writer, ImageId value, JsonSerializerOptions options)
Expand Down
Loading