Skip to content

Commit

Permalink
Merge pull request dotnet#37976 from steveharter/TechEmPower
Browse files Browse the repository at this point in the history
Perf improvements mostly for small or value-type POCOs
  • Loading branch information
steveharter authored Jun 18, 2020
2 parents a86d884 + 37ef28f commit c5a4e41
Show file tree
Hide file tree
Showing 49 changed files with 401 additions and 190 deletions.
1 change: 1 addition & 0 deletions src/libraries/System.Text.Json/src/System.Text.Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<Compile Include="System\Text\Json\JsonException.cs" />
<Compile Include="System\Text\Json\JsonHelpers.cs" />
<Compile Include="System\Text\Json\JsonHelpers.Date.cs" />
<Compile Include="System\Text\Json\JsonHelpers.Escaping.cs" />
<Compile Include="System\Text\Json\JsonTokenType.cs" />
<Compile Include="System\Text\Json\Reader\ConsumeNumberResult.cs" />
<Compile Include="System\Text\Json\Reader\ConsumeTokenResult.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,6 @@ private JsonEncodedText(byte[] utf8Value)
_utf8Value = utf8Value;
}

private JsonEncodedText(string stringValue, byte[] utf8Value)
{
Debug.Assert(stringValue != null);
Debug.Assert(utf8Value != null);

_value = stringValue;
_utf8Value = utf8Value;
}

/// <summary>
/// Encodes the string text value as a JSON string.
/// </summary>
Expand Down Expand Up @@ -126,70 +117,14 @@ private static JsonEncodedText EncodeHelper(ReadOnlySpan<byte> utf8Value, JavaSc

if (idx != -1)
{
return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
return new JsonEncodedText(JsonHelpers.EscapeValue(utf8Value, idx, encoder));
}
else
{
return new JsonEncodedText(utf8Value.ToArray());
}
}

/// <summary>
/// Internal version that keeps the existing string and byte[] references if there is no escaping required.
/// </summary>
internal static JsonEncodedText Encode(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder = null)
{
Debug.Assert(stringValue.Equals(JsonHelpers.Utf8GetString(utf8Value)));

if (utf8Value.Length == 0)
{
return new JsonEncodedText(stringValue, utf8Value);
}

JsonWriterHelper.ValidateValue(utf8Value);
return EncodeHelper(stringValue, utf8Value, encoder);
}

private static JsonEncodedText EncodeHelper(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder)
{
int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);

if (idx != -1)
{
return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
}
else
{
// Encoding is not necessary; use the same stringValue and utf8Value references.
return new JsonEncodedText(stringValue, utf8Value);
}
}

private static byte[] GetEscapedString(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal, JavaScriptEncoder? encoder)
{
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);

byte[]? valueArray = null;

int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);

Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
(valueArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);

byte[] escapedString = escapedValue.Slice(0, written).ToArray();

if (valueArray != null)
{
ArrayPool<byte>.Shared.Return(valueArray);
}

return escapedString;
}

/// <summary>
/// Determines whether this instance and another specified <see cref="JsonEncodedText"/> instance have the same value.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;

namespace System.Text.Json
{
internal static partial class JsonHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan<byte> utf8Value, JavaScriptEncoder? encoder)
{
int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);

if (idx != -1)
{
return GetEscapedPropertyNameSection(utf8Value, idx, encoder);
}
else
{
return GetPropertyNameSection(utf8Value);
}
}

public static byte[] EscapeValue(
ReadOnlySpan<byte> utf8Value,
int firstEscapeIndexVal,
JavaScriptEncoder? encoder)
{
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);

byte[]? valueArray = null;

int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);

Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
(valueArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);

byte[] escapedString = escapedValue.Slice(0, written).ToArray();

if (valueArray != null)
{
ArrayPool<byte>.Shared.Return(valueArray);
}

return escapedString;
}

private static byte[] GetEscapedPropertyNameSection(
ReadOnlySpan<byte> utf8Value,
int firstEscapeIndexVal,
JavaScriptEncoder? encoder)
{
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);

byte[]? valueArray = null;

int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);

Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
stackalloc byte[length] :
(valueArray = ArrayPool<byte>.Shared.Rent(length));

JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);

byte[] propertySection = GetPropertyNameSection(escapedValue.Slice(0, written));

if (valueArray != null)
{
ArrayPool<byte>.Shared.Return(valueArray);
}

return propertySection;
}

private static byte[] GetPropertyNameSection(ReadOnlySpan<byte> utf8Value)
{
int length = utf8Value.Length;
byte[] propertySection = new byte[length + 3];

propertySection[0] = JsonConstants.Quote;
utf8Value.CopyTo(propertySection.AsSpan(1, length));
propertySection[++length] = JsonConstants.Quote;
propertySection[++length] = JsonConstants.KeyValueSeperator;

return propertySection;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public static void ReadWithVerify(this ref Utf8JsonReader reader)
/// </summary>
/// <param name="bytes">The utf8 bytes to convert.</param>
/// <returns></returns>
internal static string Utf8GetString(ReadOnlySpan<byte> bytes)
public static string Utf8GetString(ReadOnlySpan<byte> bytes)
{
return Encoding.UTF8.GetString(bytes
#if NETSTANDARD2_0 || NETFRAMEWORK
Expand All @@ -103,7 +103,7 @@ internal static string Utf8GetString(ReadOnlySpan<byte> bytes)
/// <summary>
/// Emulates Dictionary.TryAdd on netstandard.
/// </summary>
internal static bool TryAdd<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key, TValue value) where TKey : notnull
public static bool TryAdd<TKey, TValue>(Dictionary<TKey, TValue> dictionary, in TKey key, in TValue value) where TKey : notnull
{
#if NETSTANDARD2_0 || NETFRAMEWORK
if (!dictionary.ContainsKey(key))
Expand All @@ -118,7 +118,7 @@ internal static bool TryAdd<TKey, TValue>(Dictionary<TKey, TValue> dictionary, T
#endif
}

internal static bool IsFinite(double value)
public static bool IsFinite(double value)
{
#if BUILDING_INBOX_LIBRARY
return double.IsFinite(value);
Expand All @@ -127,7 +127,7 @@ internal static bool IsFinite(double value)
#endif
}

internal static bool IsFinite(float value)
public static bool IsFinite(float value)
{
#if BUILDING_INBOX_LIBRARY
return float.IsFinite(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal sealed class ArrayConverter<TCollection, TElement>
{
internal override bool CanHaveIdMetadata => false;

protected override void Add(TElement value, ref ReadStack state)
protected override void Add(in TElement value, ref ReadStack state)
{
((List<TElement>)state.Current.ReturnValue!).Add(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal sealed class ConcurrentQueueOfTConverter<TCollection, TElement>
: IEnumerableDefaultConverter<TCollection, TElement>
where TCollection : ConcurrentQueue<TElement>
{
protected override void Add(TElement value, ref ReadStack state)
protected override void Add(in TElement value, ref ReadStack state)
{
((TCollection)state.Current.ReturnValue!).Enqueue(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal sealed class ConcurrentStackOfTConverter<TCollection, TElement>
: IEnumerableDefaultConverter<TCollection, TElement>
where TCollection : ConcurrentStack<TElement>
{
protected override void Add(TElement value, ref ReadStack state)
protected override void Add(in TElement value, ref ReadStack state)
{
((TCollection)state.Current.ReturnValue!).Push(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal abstract class DictionaryDefaultConverter<TCollection, TValue>
/// <summary>
/// When overridden, adds the value to the collection.
/// </summary>
protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
protected abstract void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state);

/// <summary>
/// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal sealed class DictionaryOfStringTValueConverter<TCollection, TValue>
: DictionaryDefaultConverter<TCollection, TValue>
where TCollection : Dictionary<string, TValue>
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
protected override void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state)
{
string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal sealed class ICollectionOfTConverter<TCollection, TElement>
: IEnumerableDefaultConverter<TCollection, TElement>
where TCollection : ICollection<TElement>
{
protected override void Add(TElement value, ref ReadStack state)
protected override void Add(in TElement value, ref ReadStack state)
{
((ICollection<TElement>)state.Current.ReturnValue!).Add(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal sealed class IDictionaryConverter<TCollection>
: DictionaryDefaultConverter<TCollection, object?>
where TCollection : IDictionary
{
protected override void Add(object? value, JsonSerializerOptions options, ref ReadStack state)
protected override void Add(in object? value, JsonSerializerOptions options, ref ReadStack state)
{
string key = state.Current.JsonPropertyNameAsString!;
((IDictionary)state.Current.ReturnValue!)[key] = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal sealed class IDictionaryOfStringTValueConverter<TCollection, TValue>
: DictionaryDefaultConverter<TCollection, TValue>
where TCollection : IDictionary<string, TValue>
{
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
protected override void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state)
{
string key = state.Current.JsonPropertyNameAsString!;
((TCollection)state.Current.ReturnValue!)[key] = value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal sealed class IEnumerableConverter<TCollection>
: IEnumerableDefaultConverter<TCollection, object?>
where TCollection : IEnumerable
{
protected override void Add(object? value, ref ReadStack state)
protected override void Add(in object? value, ref ReadStack state)
{
((List<object?>)state.Current.ReturnValue!).Add(value);
}
Expand Down Expand Up @@ -62,7 +62,8 @@ protected override bool OnWriteResume(
return false;
}

if (!converter.TryWrite(writer, enumerator.Current, options, ref state))
object? element = enumerator.Current;
if (!converter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal static class IEnumerableConverterFactoryHelpers

Type? baseTypeToCheck = type;

while (baseTypeToCheck != null && baseTypeToCheck != typeof(object))
while (baseTypeToCheck != null && baseTypeToCheck != JsonClassInfo.ObjectType)
{
if (baseTypeToCheck.IsGenericType)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace System.Text.Json.Serialization.Converters
internal abstract class IEnumerableDefaultConverter<TCollection, TElement>
: JsonCollectionConverter<TCollection, TElement>
{
protected abstract void Add(TElement value, ref ReadStack state);
protected abstract void Add(in TElement value, ref ReadStack state);
protected abstract void CreateCollection(ref Utf8JsonReader reader, ref ReadStack state, JsonSerializerOptions options);
protected virtual void ConvertCollection(ref ReadStack state, JsonSerializerOptions options) { }

Expand Down Expand Up @@ -242,7 +242,11 @@ internal override bool OnTryRead(
return true;
}

internal sealed override bool OnTryWrite(Utf8JsonWriter writer, TCollection value, JsonSerializerOptions options, ref WriteStack state)
internal sealed override bool OnTryWrite(
Utf8JsonWriter writer,
TCollection value,
JsonSerializerOptions options,
ref WriteStack state)
{
bool success;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal sealed class IEnumerableOfTConverter<TCollection, TElement>
: IEnumerableDefaultConverter<TCollection, TElement>
where TCollection : IEnumerable<TElement>
{
protected override void Add(TElement value, ref ReadStack state)
protected override void Add(in TElement value, ref ReadStack state)
{
((List<TElement>)state.Current.ReturnValue!).Add(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ internal sealed class IEnumerableWithAddMethodConverter<TCollection>
: IEnumerableDefaultConverter<TCollection, object?>
where TCollection : IEnumerable
{
protected override void Add(object? value, ref ReadStack state)
protected override void Add(in object? value, ref ReadStack state)
{
((Action<TCollection, object?>)state.Current.AddMethodDelegate!)((TCollection)state.Current.ReturnValue!, value);
}
Expand Down Expand Up @@ -53,7 +53,8 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
return false;
}

if (!converter.TryWrite(writer, enumerator.Current, options, ref state))
object? element = enumerator.Current;
if (!converter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal sealed class IListConverter<TCollection>
: IEnumerableDefaultConverter<TCollection, object?>
where TCollection : IList
{
protected override void Add(object? value, ref ReadStack state)
protected override void Add(in object? value, ref ReadStack state)
{
((IList)state.Current.ReturnValue!).Add(value);
}
Expand Down Expand Up @@ -74,7 +74,6 @@ protected override bool OnWriteResume(Utf8JsonWriter writer, TCollection value,
}

object? element = enumerator.Current;

if (!converter.TryWrite(writer, element, options, ref state))
{
state.Current.CollectionEnumerator = enumerator;
Expand Down
Loading

0 comments on commit c5a4e41

Please sign in to comment.