Skip to content

Commit c5a4e41

Browse files
authored
Merge pull request #37976 from steveharter/TechEmPower
Perf improvements mostly for small or value-type POCOs
2 parents a86d884 + 37ef28f commit c5a4e41

File tree

49 files changed

+401
-190
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+401
-190
lines changed

src/libraries/System.Text.Json/src/System.Text.Json.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<Compile Include="System\Text\Json\JsonException.cs" />
4040
<Compile Include="System\Text\Json\JsonHelpers.cs" />
4141
<Compile Include="System\Text\Json\JsonHelpers.Date.cs" />
42+
<Compile Include="System\Text\Json\JsonHelpers.Escaping.cs" />
4243
<Compile Include="System\Text\Json\JsonTokenType.cs" />
4344
<Compile Include="System\Text\Json\Reader\ConsumeNumberResult.cs" />
4445
<Compile Include="System\Text\Json\Reader\ConsumeTokenResult.cs" />

src/libraries/System.Text.Json/src/System/Text/Json/JsonEncodedText.cs

Lines changed: 1 addition & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,6 @@ private JsonEncodedText(byte[] utf8Value)
3232
_utf8Value = utf8Value;
3333
}
3434

35-
private JsonEncodedText(string stringValue, byte[] utf8Value)
36-
{
37-
Debug.Assert(stringValue != null);
38-
Debug.Assert(utf8Value != null);
39-
40-
_value = stringValue;
41-
_utf8Value = utf8Value;
42-
}
43-
4435
/// <summary>
4536
/// Encodes the string text value as a JSON string.
4637
/// </summary>
@@ -126,70 +117,14 @@ private static JsonEncodedText EncodeHelper(ReadOnlySpan<byte> utf8Value, JavaSc
126117

127118
if (idx != -1)
128119
{
129-
return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
120+
return new JsonEncodedText(JsonHelpers.EscapeValue(utf8Value, idx, encoder));
130121
}
131122
else
132123
{
133124
return new JsonEncodedText(utf8Value.ToArray());
134125
}
135126
}
136127

137-
/// <summary>
138-
/// Internal version that keeps the existing string and byte[] references if there is no escaping required.
139-
/// </summary>
140-
internal static JsonEncodedText Encode(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder = null)
141-
{
142-
Debug.Assert(stringValue.Equals(JsonHelpers.Utf8GetString(utf8Value)));
143-
144-
if (utf8Value.Length == 0)
145-
{
146-
return new JsonEncodedText(stringValue, utf8Value);
147-
}
148-
149-
JsonWriterHelper.ValidateValue(utf8Value);
150-
return EncodeHelper(stringValue, utf8Value, encoder);
151-
}
152-
153-
private static JsonEncodedText EncodeHelper(string stringValue, byte[] utf8Value, JavaScriptEncoder? encoder)
154-
{
155-
int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);
156-
157-
if (idx != -1)
158-
{
159-
return new JsonEncodedText(GetEscapedString(utf8Value, idx, encoder));
160-
}
161-
else
162-
{
163-
// Encoding is not necessary; use the same stringValue and utf8Value references.
164-
return new JsonEncodedText(stringValue, utf8Value);
165-
}
166-
}
167-
168-
private static byte[] GetEscapedString(ReadOnlySpan<byte> utf8Value, int firstEscapeIndexVal, JavaScriptEncoder? encoder)
169-
{
170-
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
171-
Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
172-
173-
byte[]? valueArray = null;
174-
175-
int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
176-
177-
Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
178-
stackalloc byte[length] :
179-
(valueArray = ArrayPool<byte>.Shared.Rent(length));
180-
181-
JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
182-
183-
byte[] escapedString = escapedValue.Slice(0, written).ToArray();
184-
185-
if (valueArray != null)
186-
{
187-
ArrayPool<byte>.Shared.Return(valueArray);
188-
}
189-
190-
return escapedString;
191-
}
192-
193128
/// <summary>
194129
/// Determines whether this instance and another specified <see cref="JsonEncodedText"/> instance have the same value.
195130
/// </summary>
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Buffers;
6+
using System.Diagnostics;
7+
using System.Runtime.CompilerServices;
8+
using System.Text.Encodings.Web;
9+
10+
namespace System.Text.Json
11+
{
12+
internal static partial class JsonHelpers
13+
{
14+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
15+
public static byte[] GetEscapedPropertyNameSection(ReadOnlySpan<byte> utf8Value, JavaScriptEncoder? encoder)
16+
{
17+
int idx = JsonWriterHelper.NeedsEscaping(utf8Value, encoder);
18+
19+
if (idx != -1)
20+
{
21+
return GetEscapedPropertyNameSection(utf8Value, idx, encoder);
22+
}
23+
else
24+
{
25+
return GetPropertyNameSection(utf8Value);
26+
}
27+
}
28+
29+
public static byte[] EscapeValue(
30+
ReadOnlySpan<byte> utf8Value,
31+
int firstEscapeIndexVal,
32+
JavaScriptEncoder? encoder)
33+
{
34+
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
35+
Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
36+
37+
byte[]? valueArray = null;
38+
39+
int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
40+
41+
Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
42+
stackalloc byte[length] :
43+
(valueArray = ArrayPool<byte>.Shared.Rent(length));
44+
45+
JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
46+
47+
byte[] escapedString = escapedValue.Slice(0, written).ToArray();
48+
49+
if (valueArray != null)
50+
{
51+
ArrayPool<byte>.Shared.Return(valueArray);
52+
}
53+
54+
return escapedString;
55+
}
56+
57+
private static byte[] GetEscapedPropertyNameSection(
58+
ReadOnlySpan<byte> utf8Value,
59+
int firstEscapeIndexVal,
60+
JavaScriptEncoder? encoder)
61+
{
62+
Debug.Assert(int.MaxValue / JsonConstants.MaxExpansionFactorWhileEscaping >= utf8Value.Length);
63+
Debug.Assert(firstEscapeIndexVal >= 0 && firstEscapeIndexVal < utf8Value.Length);
64+
65+
byte[]? valueArray = null;
66+
67+
int length = JsonWriterHelper.GetMaxEscapedLength(utf8Value.Length, firstEscapeIndexVal);
68+
69+
Span<byte> escapedValue = length <= JsonConstants.StackallocThreshold ?
70+
stackalloc byte[length] :
71+
(valueArray = ArrayPool<byte>.Shared.Rent(length));
72+
73+
JsonWriterHelper.EscapeString(utf8Value, escapedValue, firstEscapeIndexVal, encoder, out int written);
74+
75+
byte[] propertySection = GetPropertyNameSection(escapedValue.Slice(0, written));
76+
77+
if (valueArray != null)
78+
{
79+
ArrayPool<byte>.Shared.Return(valueArray);
80+
}
81+
82+
return propertySection;
83+
}
84+
85+
private static byte[] GetPropertyNameSection(ReadOnlySpan<byte> utf8Value)
86+
{
87+
int length = utf8Value.Length;
88+
byte[] propertySection = new byte[length + 3];
89+
90+
propertySection[0] = JsonConstants.Quote;
91+
utf8Value.CopyTo(propertySection.AsSpan(1, length));
92+
propertySection[++length] = JsonConstants.Quote;
93+
propertySection[++length] = JsonConstants.KeyValueSeperator;
94+
95+
return propertySection;
96+
}
97+
}
98+
}

src/libraries/System.Text.Json/src/System/Text/Json/JsonHelpers.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ public static void ReadWithVerify(this ref Utf8JsonReader reader)
9191
/// </summary>
9292
/// <param name="bytes">The utf8 bytes to convert.</param>
9393
/// <returns></returns>
94-
internal static string Utf8GetString(ReadOnlySpan<byte> bytes)
94+
public static string Utf8GetString(ReadOnlySpan<byte> bytes)
9595
{
9696
return Encoding.UTF8.GetString(bytes
9797
#if NETSTANDARD2_0 || NETFRAMEWORK
@@ -103,7 +103,7 @@ internal static string Utf8GetString(ReadOnlySpan<byte> bytes)
103103
/// <summary>
104104
/// Emulates Dictionary.TryAdd on netstandard.
105105
/// </summary>
106-
internal static bool TryAdd<TKey, TValue>(Dictionary<TKey, TValue> dictionary, TKey key, TValue value) where TKey : notnull
106+
public static bool TryAdd<TKey, TValue>(Dictionary<TKey, TValue> dictionary, in TKey key, in TValue value) where TKey : notnull
107107
{
108108
#if NETSTANDARD2_0 || NETFRAMEWORK
109109
if (!dictionary.ContainsKey(key))
@@ -118,7 +118,7 @@ internal static bool TryAdd<TKey, TValue>(Dictionary<TKey, TValue> dictionary, T
118118
#endif
119119
}
120120

121-
internal static bool IsFinite(double value)
121+
public static bool IsFinite(double value)
122122
{
123123
#if BUILDING_INBOX_LIBRARY
124124
return double.IsFinite(value);
@@ -127,7 +127,7 @@ internal static bool IsFinite(double value)
127127
#endif
128128
}
129129

130-
internal static bool IsFinite(float value)
130+
public static bool IsFinite(float value)
131131
{
132132
#if BUILDING_INBOX_LIBRARY
133133
return float.IsFinite(value);

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ArrayConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal sealed class ArrayConverter<TCollection, TElement>
1616
{
1717
internal override bool CanHaveIdMetadata => false;
1818

19-
protected override void Add(TElement value, ref ReadStack state)
19+
protected override void Add(in TElement value, ref ReadStack state)
2020
{
2121
((List<TElement>)state.Current.ReturnValue!).Add(value);
2222
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentQueueOfTConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal sealed class ConcurrentQueueOfTConverter<TCollection, TElement>
1111
: IEnumerableDefaultConverter<TCollection, TElement>
1212
where TCollection : ConcurrentQueue<TElement>
1313
{
14-
protected override void Add(TElement value, ref ReadStack state)
14+
protected override void Add(in TElement value, ref ReadStack state)
1515
{
1616
((TCollection)state.Current.ReturnValue!).Enqueue(value);
1717
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ConcurrentStackOfTConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ internal sealed class ConcurrentStackOfTConverter<TCollection, TElement>
1111
: IEnumerableDefaultConverter<TCollection, TElement>
1212
where TCollection : ConcurrentStack<TElement>
1313
{
14-
protected override void Add(TElement value, ref ReadStack state)
14+
protected override void Add(in TElement value, ref ReadStack state)
1515
{
1616
((TCollection)state.Current.ReturnValue!).Push(value);
1717
}

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryDefaultConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal abstract class DictionaryDefaultConverter<TCollection, TValue>
1616
/// <summary>
1717
/// When overridden, adds the value to the collection.
1818
/// </summary>
19-
protected abstract void Add(TValue value, JsonSerializerOptions options, ref ReadStack state);
19+
protected abstract void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state);
2020

2121
/// <summary>
2222
/// When overridden, converts the temporary collection held in state.Current.ReturnValue to the final collection.

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/DictionaryOfStringTValueConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ internal sealed class DictionaryOfStringTValueConverter<TCollection, TValue>
1414
: DictionaryDefaultConverter<TCollection, TValue>
1515
where TCollection : Dictionary<string, TValue>
1616
{
17-
protected override void Add(TValue value, JsonSerializerOptions options, ref ReadStack state)
17+
protected override void Add(in TValue value, JsonSerializerOptions options, ref ReadStack state)
1818
{
1919
string key = state.Current.JsonPropertyNameAsString!;
2020
((TCollection)state.Current.ReturnValue!)[key] = value;

src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/Collection/ICollectionOfTConverter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal sealed class ICollectionOfTConverter<TCollection, TElement>
1313
: IEnumerableDefaultConverter<TCollection, TElement>
1414
where TCollection : ICollection<TElement>
1515
{
16-
protected override void Add(TElement value, ref ReadStack state)
16+
protected override void Add(in TElement value, ref ReadStack state)
1717
{
1818
((ICollection<TElement>)state.Current.ReturnValue!).Add(value);
1919
}

0 commit comments

Comments
 (0)