Cache StringBuilder instances in the .NET JsonTextTokenizer.

COPYBARA_INTEGRATE_REVIEW=https://github.com/protocolbuffers/protobuf/pull/15794 from TrayanZapryanov:cache_stringbuilder 596147e6f1
PiperOrigin-RevId: 613251480
pull/16080/head
Trayan Zapryanov 1 year ago committed by Copybara-Service
parent 3dadd0e1ee
commit fac929d9aa
  1. 70
      csharp/src/Google.Protobuf/JsonTokenizer.cs

@ -301,7 +301,8 @@ namespace Google.Protobuf
/// </summary>
private string ReadString()
{
var value = new StringBuilder();
//builder will not be released in case of an exception, but this is not a problem and we will create new on next Acquire
var builder = StringBuilderCache.Acquire();
bool haveHighSurrogate = false;
while (true)
{
@ -316,7 +317,7 @@ namespace Google.Protobuf
{
throw reader.CreateException("Invalid use of surrogate pair code units");
}
return value.ToString();
return StringBuilderCache.GetStringAndRelease(builder);
}
if (c == '\\')
{
@ -330,7 +331,7 @@ namespace Google.Protobuf
throw reader.CreateException("Invalid use of surrogate pair code units");
}
haveHighSurrogate = char.IsHighSurrogate(c);
value.Append(c);
builder.Append(c);
}
}
@ -408,7 +409,8 @@ namespace Google.Protobuf
private double ReadNumber(char initialCharacter)
{
StringBuilder builder = new StringBuilder();
//builder will not be released in case of an exception, but this is not a problem and we will create new on next Acquire
var builder = StringBuilderCache.Acquire();
if (initialCharacter == '-')
{
builder.Append("-");
@ -437,9 +439,10 @@ namespace Google.Protobuf
}
// TODO: What exception should we throw if the value can't be represented as a double?
var builderValue = StringBuilderCache.GetStringAndRelease(builder);
try
{
double result = double.Parse(builder.ToString(),
double result = double.Parse(builderValue,
NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint | NumberStyles.AllowExponent,
CultureInfo.InvariantCulture);
@ -447,14 +450,14 @@ namespace Google.Protobuf
// For compatibility with other Protobuf implementations the tokenizer should still throw.
if (double.IsInfinity(result))
{
throw reader.CreateException("Numeric value out of range: " + builder);
throw reader.CreateException("Numeric value out of range: " + builderValue);
}
return result;
}
catch (OverflowException)
{
throw reader.CreateException("Numeric value out of range: " + builder);
throw reader.CreateException("Numeric value out of range: " + builderValue);
}
}
@ -728,6 +731,59 @@ namespace Google.Protobuf
return new InvalidJsonException(message);
}
}
/// <summary>
/// Provide a cached reusable instance of stringbuilder per thread.
/// Copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/StringBuilderCache.cs
/// </summary>
private static class StringBuilderCache
{
private const int MaxCachedStringBuilderSize = 360;
private const int DefaultStringBuilderCapacity = 16; // == StringBuilder.DefaultCapacity
[ThreadStatic]
private static StringBuilder cachedInstance;
/// <summary>Get a StringBuilder for the specified capacity.</summary>
/// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
public static StringBuilder Acquire(int capacity = DefaultStringBuilderCapacity)
{
if (capacity <= MaxCachedStringBuilderSize)
{
StringBuilder sb = cachedInstance;
if (sb != null)
{
// Avoid stringbuilder block fragmentation by getting a new StringBuilder
// when the requested size is larger than the current capacity
if (capacity <= sb.Capacity)
{
cachedInstance = null;
sb.Clear();
return sb;
}
}
}
return new StringBuilder(capacity);
}
/// <summary>Place the specified builder in the cache if it is not too big.</summary>
private static void Release(StringBuilder sb)
{
if (sb.Capacity <= MaxCachedStringBuilderSize)
{
cachedInstance = cachedInstance?.Capacity >= sb.Capacity ? cachedInstance : sb;
}
}
/// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary>
public static string GetStringAndRelease(StringBuilder sb)
{
string result = sb.ToString();
Release(sb);
return result;
}
}
}
}
}

Loading…
Cancel
Save