@@ -301,7 +301,8 @@ private void ValidateState(State validStates, string errorPrefix)
301
301
/// </summary>
302
302
private string ReadString ( )
303
303
{
304
- var value = new StringBuilder ( ) ;
304
+ //builder will not be released in case of an exception, but this is not a problem and we will create new on next Acquire
305
+ var builder = StringBuilderCache . Acquire ( ) ;
305
306
bool haveHighSurrogate = false ;
306
307
while ( true )
307
308
{
@@ -316,7 +317,7 @@ private string ReadString()
316
317
{
317
318
throw reader . CreateException ( "Invalid use of surrogate pair code units" ) ;
318
319
}
319
- return value . ToString ( ) ;
320
+ return StringBuilderCache . GetStringAndRelease ( builder ) ;
320
321
}
321
322
if ( c == '\\ ' )
322
323
{
@@ -330,7 +331,7 @@ private string ReadString()
330
331
throw reader . CreateException ( "Invalid use of surrogate pair code units" ) ;
331
332
}
332
333
haveHighSurrogate = char . IsHighSurrogate ( c ) ;
333
- value . Append ( c ) ;
334
+ builder . Append ( c ) ;
334
335
}
335
336
}
336
337
@@ -408,7 +409,8 @@ private void ConsumeLiteral(string text)
408
409
409
410
private double ReadNumber ( char initialCharacter )
410
411
{
411
- StringBuilder builder = new StringBuilder ( ) ;
412
+ //builder will not be released in case of an exception, but this is not a problem and we will create new on next Acquire
413
+ var builder = StringBuilderCache . Acquire ( ) ;
412
414
if ( initialCharacter == '-' )
413
415
{
414
416
builder . Append ( "-" ) ;
@@ -437,24 +439,25 @@ private double ReadNumber(char initialCharacter)
437
439
}
438
440
439
441
// TODO: What exception should we throw if the value can't be represented as a double?
442
+ var builderValue = StringBuilderCache . GetStringAndRelease ( builder ) ;
440
443
try
441
444
{
442
- double result = double . Parse ( builder . ToString ( ) ,
445
+ double result = double . Parse ( builderValue ,
443
446
NumberStyles . AllowLeadingSign | NumberStyles . AllowDecimalPoint | NumberStyles . AllowExponent ,
444
447
CultureInfo . InvariantCulture ) ;
445
448
446
449
// .NET Core 3.0 and later returns infinity if the number is too large or small to be represented.
447
450
// For compatibility with other Protobuf implementations the tokenizer should still throw.
448
451
if ( double . IsInfinity ( result ) )
449
452
{
450
- throw reader . CreateException ( "Numeric value out of range: " + builder ) ;
453
+ throw reader . CreateException ( "Numeric value out of range: " + builderValue ) ;
451
454
}
452
455
453
456
return result ;
454
457
}
455
458
catch ( OverflowException )
456
459
{
457
- throw reader . CreateException ( "Numeric value out of range: " + builder ) ;
460
+ throw reader . CreateException ( "Numeric value out of range: " + builderValue ) ;
458
461
}
459
462
}
460
463
@@ -728,6 +731,59 @@ internal InvalidJsonException CreateException(string message)
728
731
return new InvalidJsonException ( message ) ;
729
732
}
730
733
}
734
+
735
+ /// <summary>
736
+ /// Provide a cached reusable instance of stringbuilder per thread.
737
+ /// Copied from https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Text/StringBuilderCache.cs
738
+ /// </summary>
739
+ private static class StringBuilderCache
740
+ {
741
+ private const int MaxCachedStringBuilderSize = 360 ;
742
+ private const int DefaultStringBuilderCapacity = 16 ; // == StringBuilder.DefaultCapacity
743
+
744
+ [ ThreadStatic ]
745
+ private static StringBuilder cachedInstance ;
746
+
747
+ /// <summary>Get a StringBuilder for the specified capacity.</summary>
748
+ /// <remarks>If a StringBuilder of an appropriate size is cached, it will be returned and the cache emptied.</remarks>
749
+ public static StringBuilder Acquire ( int capacity = DefaultStringBuilderCapacity )
750
+ {
751
+ if ( capacity <= MaxCachedStringBuilderSize )
752
+ {
753
+ StringBuilder sb = cachedInstance ;
754
+ if ( sb != null )
755
+ {
756
+ // Avoid stringbuilder block fragmentation by getting a new StringBuilder
757
+ // when the requested size is larger than the current capacity
758
+ if ( capacity <= sb . Capacity )
759
+ {
760
+ cachedInstance = null ;
761
+ sb . Clear ( ) ;
762
+ return sb ;
763
+ }
764
+ }
765
+ }
766
+
767
+ return new StringBuilder ( capacity ) ;
768
+ }
769
+
770
+ /// <summary>Place the specified builder in the cache if it is not too big.</summary>
771
+ private static void Release ( StringBuilder sb )
772
+ {
773
+ if ( sb . Capacity <= MaxCachedStringBuilderSize )
774
+ {
775
+ cachedInstance = cachedInstance ? . Capacity >= sb . Capacity ? cachedInstance : sb ;
776
+ }
777
+ }
778
+
779
+ /// <summary>ToString() the stringbuilder, Release it to the cache, and return the resulting string.</summary>
780
+ public static string GetStringAndRelease ( StringBuilder sb )
781
+ {
782
+ string result = sb . ToString ( ) ;
783
+ Release ( sb ) ;
784
+ return result ;
785
+ }
786
+ }
731
787
}
732
788
}
733
789
}
0 commit comments