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

Improve serializer performance #57327

Merged
merged 7 commits into from
Aug 15, 2021
Merged
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Change main null check in TryWrite(); other misc
steveharter committed Aug 13, 2021
commit 5467cd9a4d59b94d025a0938aef67acb698a684d
Original file line number Diff line number Diff line change
@@ -70,8 +70,5 @@ internal override void WriteNumberWithCustomHandling(Utf8JsonWriter writer, T? v
_converter.WriteNumberWithCustomHandling(writer, value.Value, handling);
}
}

internal override bool IsNull(in T? value) => !value.HasValue;
internal override bool IsNullableOfT() => true;
}
}
Original file line number Diff line number Diff line change
@@ -30,12 +30,6 @@ protected internal JsonConverter()
HandleNullOnRead = true;
HandleNullOnWrite = true;
}
else
{
_autoWriteNulls = CanBeNull;
}

_isNullableOfT = IsNullableOfT();

// For the HandleNull == false case, either:
// 1) The default values are assigned in this type's virtual HandleNull property
@@ -46,17 +40,6 @@ protected internal JsonConverter()
CanUseDirectReadOrWrite = !CanBePolymorphic && IsInternalConverter && ConverterStrategy == ConverterStrategy.Value;
}

// <summary>
// Optimized check for 'CanBeNull && !HandleNullOnWrite'
// </summary>
private bool _autoWriteNulls;


// <summary>
// Optimized check to check if converter is for Nullable<T>
// </summary>
private bool _isNullableOfT;

/// <summary>
/// Determines whether the type can be converted.
/// </summary>
@@ -331,16 +314,9 @@ internal override sealed bool TryReadAsObject(ref Utf8JsonReader reader, JsonSer
}

/// <summary>
/// Performance optimization. Overridden by the nullable converter to prevent boxing of values by the JIT.
/// Although the JIT added support to detect this IL pattern (box+isinst+br), it still manifests in TryWrite().
/// </summary>
internal virtual bool IsNull(in T value) => value is null;

/// <summary>
/// Performance optimization to determine if IsNull() above should be called.
/// Performance optimization. The 'in' modifier in TryWrite(in T Value) will cause boxing, so this helper method avoids that.
steveharter marked this conversation as resolved.
Show resolved Hide resolved
/// </summary>
/// <returns></returns>
internal virtual bool IsNullableOfT() => false;
internal bool IsNull(T value) => value is null;
steveharter marked this conversation as resolved.
Show resolved Hide resolved

#if NET6_0_OR_GREATER
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
Copy link
Member

@eiriktsarpalis eiriktsarpalis Aug 13, 2021

Choose a reason for hiding this comment

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

I was not able to squeeze out any performance gains when applying aggressive optimizations to this method in the past.

@@ -352,24 +328,12 @@ internal bool TryWrite(Utf8JsonWriter writer, in T value, JsonSerializerOptions
ThrowHelper.ThrowJsonException_SerializerCycleDetected(options.EffectiveMaxDepth);
}

// We do not pass null values to converters unless HandleNullOnWrite is true. Null values for properties were
// already handled in GetMemberAndWriteJson() so we don't need to check for IgnoreNullValues here.
if (_autoWriteNulls)
if (default(T) is null && !HandleNullOnWrite && IsNull(value))
{
// Optimized check to see if 'IsNull()' or 'is null' should be used.
if (_isNullableOfT)
{
if (IsNull(value))
{
writer.WriteNullValue();
return true;
}
}
else if (value is null)
{
writer.WriteNullValue();
return true;
}
// We do not pass null values to converters unless HandleNullOnWrite is true. Null values for properties were
// already handled in GetMemberAndWriteJson() so we don't need to check for IgnoreNullValues here.
writer.WriteNullValue();
return true;
}

bool ignoreCyclesPopReference = false;
Original file line number Diff line number Diff line change
@@ -49,7 +49,7 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
return ReadSpan<TValue>(json.AsSpan(), jsonTypeInfo);
return ReadFromSpan<TValue>(json.AsSpan(), jsonTypeInfo);
}

/// <summary>
@@ -81,7 +81,7 @@ public static partial class JsonSerializer
// default/null span is treated as empty

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
return ReadSpan<TValue>(json, jsonTypeInfo);
return ReadFromSpan<TValue>(json, jsonTypeInfo);
}

/// <summary>
@@ -125,7 +125,7 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadSpan<object?>(json.AsSpan(), jsonTypeInfo)!;
return ReadFromSpan<object?>(json.AsSpan(), jsonTypeInfo)!;
}

/// <summary>
@@ -171,7 +171,7 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, returnType);
return ReadSpan<object?>(json, jsonTypeInfo)!;
return ReadFromSpan<object?>(json, jsonTypeInfo)!;
}

/// <summary>
@@ -218,7 +218,7 @@ public static partial class JsonSerializer
throw new ArgumentNullException(nameof(jsonTypeInfo));
}

return ReadSpan<TValue?>(json.AsSpan(), jsonTypeInfo);
return ReadFromSpan<TValue?>(json.AsSpan(), jsonTypeInfo);
}

/// <summary>
@@ -260,7 +260,7 @@ public static partial class JsonSerializer
throw new ArgumentNullException(nameof(jsonTypeInfo));
}

return ReadSpan<TValue?>(json, jsonTypeInfo);
return ReadFromSpan<TValue?>(json, jsonTypeInfo);
}

/// <summary>
@@ -316,7 +316,7 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadSpan<object?>(json.AsSpan(), jsonTypeInfo);
return ReadFromSpan<object?>(json.AsSpan(), jsonTypeInfo);
}

/// <summary>
@@ -367,10 +367,10 @@ public static partial class JsonSerializer
}

JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, returnType);
return ReadSpan<object?>(json, jsonTypeInfo);
return ReadFromSpan<object?>(json, jsonTypeInfo);
}

private static TValue? ReadSpan<TValue>(ReadOnlySpan<char> json, JsonTypeInfo jsonTypeInfo)
private static TValue? ReadFromSpan<TValue>(ReadOnlySpan<char> json, JsonTypeInfo jsonTypeInfo)
{
byte[]? tempArray = null;

Original file line number Diff line number Diff line change
@@ -24,7 +24,8 @@ public static byte[] SerializeToUtf8Bytes<TValue>(
TValue value,
JsonSerializerOptions? options = null)
{
return WriteCoreBytes(value, GetRuntimeType(value), options);
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, typeof(TValue));
return WriteBytesUsingSerializer(value, jsonTypeInfo);
}

/// <summary>
@@ -50,10 +51,9 @@ public static byte[] SerializeToUtf8Bytes(
Type inputType,
JsonSerializerOptions? options = null)
{
return WriteCoreBytes(
value!,
GetRuntimeTypeAndValidateInputType(value, inputType),
options);
Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
return WriteBytesUsingSerializer(value, jsonTypeInfo);
}

/// <summary>
@@ -108,14 +108,8 @@ public static byte[] SerializeToUtf8Bytes(object? value, Type inputType, JsonSer
}

Type runtimeType = GetRuntimeTypeAndValidateInputType(value, inputType);
return WriteBytesUsingGeneratedSerializer(value!, GetTypeInfo(context, runtimeType));
}

[RequiresUnreferencedCode(SerializationUnreferencedCodeMessage)]
private static byte[] WriteCoreBytes<TValue>(in TValue value, Type runtimeType, JsonSerializerOptions? options)
{
JsonTypeInfo jsonTypeInfo = GetTypeInfo(options, runtimeType);
return WriteBytesUsingSerializer(value, jsonTypeInfo);
JsonTypeInfo jsonTypeInfo = GetTypeInfo(context, runtimeType);
return WriteBytesUsingGeneratedSerializer(value!, jsonTypeInfo);
}

private static byte[] WriteBytesUsingGeneratedSerializer<TValue>(in TValue value, JsonTypeInfo jsonTypeInfo)
Original file line number Diff line number Diff line change
@@ -37,6 +37,8 @@ private static bool WriteCore<TValue>(

private static void WriteUsingGeneratedSerializer<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
{
Debug.Assert(writer != null);

if (jsonTypeInfo.HasSerialize &&
jsonTypeInfo is JsonTypeInfo<TValue> typedInfo &&
typedInfo.Options._context?.CanUseSerializationLogic == true)
@@ -53,7 +55,10 @@ jsonTypeInfo is JsonTypeInfo<TValue> typedInfo &&

private static void WriteUsingSerializer<TValue>(Utf8JsonWriter writer, in TValue value, JsonTypeInfo jsonTypeInfo)
{
Debug.Assert(jsonTypeInfo is not JsonTypeInfo<TValue>, "Incorrect method called.");
Debug.Assert(writer != null);

Debug.Assert(!jsonTypeInfo.HasSerialize ||
jsonTypeInfo is not JsonTypeInfo<TValue>, "Incorrect method called.");

WriteStack state = default;
state.Initialize(jsonTypeInfo, supportContinuation: false);