diff --git a/csharp/src/Google.Protobuf/JsonTokenizer.cs b/csharp/src/Google.Protobuf/JsonTokenizer.cs
index 16daa5626da66..d80eed98bddfa 100644
--- a/csharp/src/Google.Protobuf/JsonTokenizer.cs
+++ b/csharp/src/Google.Protobuf/JsonTokenizer.cs
@@ -301,7 +301,8 @@ private void ValidateState(State validStates, string errorPrefix)
///
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 @@ private string ReadString()
{
throw reader.CreateException("Invalid use of surrogate pair code units");
}
- return value.ToString();
+ return StringBuilderCache.GetStringAndRelease(builder);
}
if (c == '\\')
{
@@ -330,7 +331,7 @@ private string ReadString()
throw reader.CreateException("Invalid use of surrogate pair code units");
}
haveHighSurrogate = char.IsHighSurrogate(c);
- value.Append(c);
+ builder.Append(c);
}
}
@@ -408,7 +409,8 @@ private void ConsumeLiteral(string text)
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 @@ private double ReadNumber(char initialCharacter)
}
// 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 @@ private double ReadNumber(char initialCharacter)
// 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 @@ internal InvalidJsonException CreateException(string message)
return new InvalidJsonException(message);
}
}
+
+ ///
+ /// 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
+ ///
+ private static class StringBuilderCache
+ {
+ private const int MaxCachedStringBuilderSize = 360;
+ private const int DefaultStringBuilderCapacity = 16; // == StringBuilder.DefaultCapacity
+
+ [ThreadStatic]
+ private static StringBuilder cachedInstance;
+
+ /// Get a StringBuilder for the specified capacity.
+ /// If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.
+ 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);
+ }
+
+ /// Place the specified builder in the cache if it is not too big.
+ private static void Release(StringBuilder sb)
+ {
+ if (sb.Capacity <= MaxCachedStringBuilderSize)
+ {
+ cachedInstance = cachedInstance?.Capacity >= sb.Capacity ? cachedInstance : sb;
+ }
+ }
+
+ /// ToString() the stringbuilder, Release it to the cache, and return the resulting string.
+ public static string GetStringAndRelease(StringBuilder sb)
+ {
+ string result = sb.ToString();
+ Release(sb);
+ return result;
+ }
+ }
}
}
}