Skip to content
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

Micro optimizations for serialization #39323

Merged
merged 2 commits into from
Jul 15, 2020
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 @@ -14,7 +14,8 @@ public override short Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSe

public override void Write(Utf8JsonWriter writer, short value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
// For performance, lift up the writer implementation.
writer.WriteNumberValue((long)value);
}

internal override short ReadWithQuotes(ref Utf8JsonReader reader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeri

public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
// For performance, lift up the writer implementation.
writer.WriteNumberValue((long)value);
}

internal override int ReadWithQuotes(ref Utf8JsonReader reader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,15 @@ internal sealed class StringConverter : JsonConverter<string>

public override void Write(Utf8JsonWriter writer, string? value, JsonSerializerOptions options)
{
writer.WriteStringValue(value);
// For performance, lift up the writer implementation.
if (value == null)
{
writer.WriteNullValue();
}
else
{
writer.WriteStringValue(value.AsSpan());
}
}

internal override string ReadWithQuotes(ref Utf8JsonReader reader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public override ushort Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS

public override void Write(Utf8JsonWriter writer, ushort value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
// For performance, lift up the writer implementation.
writer.WriteNumberValue((long)value);
}

internal override ushort ReadWithQuotes(ref Utf8JsonReader reader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public override uint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSer

public override void Write(Utf8JsonWriter writer, uint value, JsonSerializerOptions options)
{
writer.WriteNumberValue(value);
// For performance, lift up the writer implementation.
writer.WriteNumberValue((ulong)value);
}

internal override uint ReadWithQuotes(ref Utf8JsonReader reader)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ public sealed partial class Utf8JsonWriter
/// Thrown if this would result in invalid JSON being written (while validation is enabled).
/// </exception>
public void WriteBase64String(JsonEncodedText propertyName, ReadOnlySpan<byte> bytes)
=> WriteBase64StringHelper(propertyName.EncodedUtf8Bytes, bytes);

private void WriteBase64StringHelper(ReadOnlySpan<byte> utf8PropertyName, ReadOnlySpan<byte> bytes)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

JsonWriterHelper.ValidateBytes(bytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ public sealed partial class Utf8JsonWriter
/// The property name should already be escaped when the instance of <see cref="JsonEncodedText"/> was created.
/// </remarks>
public void WriteString(JsonEncodedText propertyName, DateTime value)
=> WriteStringHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteStringHelper(ReadOnlySpan<byte> utf8PropertyName, DateTime value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteStringByOptions(utf8PropertyName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public sealed partial class Utf8JsonWriter
/// Writes the <see cref="DateTimeOffset"/> using the round-trippable ('O') <see cref="StandardFormat"/> , for example: 2017-06-12T05:30:45.7680000-07:00.
/// </remarks>
public void WriteString(JsonEncodedText propertyName, DateTimeOffset value)
=> WriteStringHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteStringHelper(ReadOnlySpan<byte> utf8PropertyName, DateTimeOffset value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteStringByOptions(utf8PropertyName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public sealed partial class Utf8JsonWriter
/// Writes the <see cref="decimal"/> using the default <see cref="StandardFormat"/> (that is, 'G').
/// </remarks>
public void WriteNumber(JsonEncodedText propertyName, decimal value)
=> WriteNumberHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteNumberHelper(ReadOnlySpan<byte> utf8PropertyName, decimal value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteNumberByOptions(utf8PropertyName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public sealed partial class Utf8JsonWriter
/// Writes the <see cref="double"/> using the default <see cref="StandardFormat"/> (that is, 'G').
/// </remarks>
public void WriteNumber(JsonEncodedText propertyName, double value)
=> WriteNumberHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteNumberHelper(ReadOnlySpan<byte> utf8PropertyName, double value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

JsonWriterHelper.ValidateDouble(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public sealed partial class Utf8JsonWriter
/// Writes the <see cref="float"/> using the default <see cref="StandardFormat"/> (that is, 'G').
/// </remarks>
public void WriteNumber(JsonEncodedText propertyName, float value)
=> WriteNumberHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteNumberHelper(ReadOnlySpan<byte> utf8PropertyName, float value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

JsonWriterHelper.ValidateSingle(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public sealed partial class Utf8JsonWriter
/// Writes the <see cref="Guid"/> using the default <see cref="StandardFormat"/> (that is, 'D'), as the form: nnnnnnnn-nnnn-nnnn-nnnn-nnnnnnnnnnnn.
/// </remarks>
public void WriteString(JsonEncodedText propertyName, Guid value)
=> WriteStringHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteStringHelper(ReadOnlySpan<byte> utf8PropertyName, Guid value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteStringByOptions(utf8PropertyName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,8 @@ public sealed partial class Utf8JsonWriter
/// Writes the <see cref="long"/> using the default <see cref="StandardFormat"/> (that is, 'G'), for example: 32767.
/// </remarks>
public void WriteNumber(JsonEncodedText propertyName, long value)
=> WriteNumberHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteNumberHelper(ReadOnlySpan<byte> utf8PropertyName, long value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteNumberByOptions(utf8PropertyName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,8 @@ public sealed partial class Utf8JsonWriter
/// </remarks>
[CLSCompliant(false)]
public void WriteNumber(JsonEncodedText propertyName, ulong value)
=> WriteNumberHelper(propertyName.EncodedUtf8Bytes, value);

private void WriteNumberHelper(ReadOnlySpan<byte> utf8PropertyName, ulong value)
{
ReadOnlySpan<byte> utf8PropertyName = propertyName.EncodedUtf8Bytes;
Debug.Assert(utf8PropertyName.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteNumberByOptions(utf8PropertyName, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ public void WriteBase64StringValue(ReadOnlySpan<byte> bytes)

private void WriteBase64ByOptions(ReadOnlySpan<byte> bytes)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ public sealed partial class Utf8JsonWriter
/// </remarks>
public void WriteStringValue(DateTime value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteStringValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ public sealed partial class Utf8JsonWriter
/// </remarks>
public void WriteStringValue(DateTimeOffset value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteStringValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ public sealed partial class Utf8JsonWriter
/// </remarks>
public void WriteNumberValue(decimal value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteNumberValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ public void WriteNumberValue(double value)
{
JsonWriterHelper.ValidateDouble(value);

ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteNumberValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ public void WriteNumberValue(float value)
{
JsonWriterHelper.ValidateSingle(value);

ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteNumberValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ internal void WriteNumberValue(ReadOnlySpan<byte> utf8FormattedNumber)
{
JsonWriterHelper.ValidateValue(utf8FormattedNumber);
JsonWriterHelper.ValidateNumber(utf8FormattedNumber);
ValidateWritingValue();

if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ public sealed partial class Utf8JsonWriter
/// </remarks>
public void WriteStringValue(Guid value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteStringValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,24 @@ public sealed partial class Utf8JsonWriter
{
private void ValidateWritingValue()
Copy link
Member

Choose a reason for hiding this comment

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

Why not make this an aggressive-inlining method whose implementation is if (!skip) { ValidateWritingValueSlowNonInlined(); }? Then you don't have to change a dozen call sites.

Copy link
Member

Choose a reason for hiding this comment

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

@GrabYourPitchforks Mono interpreter currently can't inline methods with branches, so it would no longer provide any benefit there.

Copy link
Member

@jkotas jkotas Jul 15, 2020

Choose a reason for hiding this comment

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

It does not feel right to be doing manual inlining and making the code less maintainable just to make it run faster under interpreter.

The interpreter is always going to be a low throughput environment.

Copy link
Member Author

Choose a reason for hiding this comment

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

Avoiding the overhead of calling a no-op method is a normal idiom.

Also the existing writer code already had the same up-front if check before calling ValidateStart() and ValidateEnd() (instead of within the called method) so one way to think about this is extending that existing pattern to ValidateWritingValue. Granted there were only 4 cases of that before and now there's 21.

{
if (!_options.SkipValidation)
Debug.Assert(!_options.SkipValidation);

if (_inObject)
{
if (_inObject)
if (_tokenType != JsonTokenType.PropertyName)
{
if (_tokenType != JsonTokenType.PropertyName)
{
Debug.Assert(_tokenType != JsonTokenType.None && _tokenType != JsonTokenType.StartArray);
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWriteValueWithinObject, currentDepth: default, token: default, _tokenType);
}
Debug.Assert(_tokenType != JsonTokenType.None && _tokenType != JsonTokenType.StartArray);
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWriteValueWithinObject, currentDepth: default, token: default, _tokenType);
}
else
{
Debug.Assert(_tokenType != JsonTokenType.PropertyName);
}
else
{
Debug.Assert(_tokenType != JsonTokenType.PropertyName);

// It is more likely for CurrentDepth to not equal 0 when writing valid JSON, so check that first to rely on short-circuiting and return quickly.
if (CurrentDepth == 0 && _tokenType != JsonTokenType.None)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWriteValueAfterPrimitiveOrClose, currentDepth: default, token: default, _tokenType);
}
// It is more likely for CurrentDepth to not equal 0 when writing valid JSON, so check that first to rely on short-circuiting and return quickly.
if (CurrentDepth == 0 && _tokenType != JsonTokenType.None)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.CannotWriteValueAfterPrimitiveOrClose, currentDepth: default, token: default, _tokenType);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ public void WriteBooleanValue(bool value)

private void WriteLiteralByOptions(ReadOnlySpan<byte> utf8Value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteLiteralIndented(utf8Value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ public void WriteNumberValue(int value)
/// </remarks>
public void WriteNumberValue(long value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteNumberValueIndented(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ public sealed partial class Utf8JsonWriter
/// Thrown if this would result in invalid JSON being written (while validation is enabled).
/// </exception>
public void WriteStringValue(JsonEncodedText value)
=> WriteStringValueHelper(value.EncodedUtf8Bytes);

private void WriteStringValueHelper(ReadOnlySpan<byte> utf8Value)
{
ReadOnlySpan<byte> utf8Value = value.EncodedUtf8Bytes;
Debug.Assert(utf8Value.Length <= JsonConstants.MaxUnescapedTokenSize);

WriteStringByOptions(utf8Value);
Expand Down Expand Up @@ -99,7 +97,11 @@ private void WriteStringEscape(ReadOnlySpan<char> value)

private void WriteStringByOptions(ReadOnlySpan<char> value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteStringIndented(value);
Expand Down Expand Up @@ -242,7 +244,11 @@ private void WriteStringEscape(ReadOnlySpan<byte> utf8Value)

private void WriteStringByOptions(ReadOnlySpan<byte> utf8Value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteStringIndented(utf8Value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ public void WriteNumberValue(uint value)
[CLSCompliant(false)]
public void WriteNumberValue(ulong value)
{
ValidateWritingValue();
if (!_options.SkipValidation)
{
ValidateWritingValue();
}

if (_options.Indented)
{
WriteNumberValueIndented(value);
Expand Down