Skip to content

Commit 941ec76

Browse files
committed
Increase size of Number's small numbers cache, and make it lazy
1 parent e7ee837 commit 941ec76

File tree

1 file changed

+44
-13
lines changed

1 file changed

+44
-13
lines changed

src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,23 @@ internal static partial class Number
266266
private const int CharStackBufferSize = 32;
267267
private const string PosNumberFormat = "#";
268268

269-
private static readonly string[] s_singleDigitStringCache = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
269+
/// <summary>The non-inclusive upper bound of <see cref="s_smallNumberCache"/>.</summary>
270+
/// <remarks>
271+
/// This is a semi-arbitrary bound. For mono, which is often used for more size-constrained workloads,
272+
/// we keep the size really small, supporting only single digit values. For coreclr, we use a larger
273+
/// value, still relatively small but large enough to accomodate common sources of numbers to strings, e.g. HTTP success status codes.
274+
/// By being >= 255, it also accomodates all byte.ToString()s. If no small numbers are ever formatted, we incur
275+
/// the ~2400 bytes on 64-bit for the array itself. If all small numbers are formatted, we incur ~11,500 bytes
276+
/// on 64-bit for the array and all the strings.
277+
/// </remarks>
278+
private const int SmallNumberCacheLength =
279+
#if MONO
280+
10;
281+
#else
282+
300;
283+
#endif
284+
/// <summary>Lazily-populated cache of strings for uint values in the range [0, <see cref="SmallNumberCacheLength"/>).</summary>
285+
private static readonly string[] s_smallNumberCache = new string[SmallNumberCacheLength];
270286

271287
private static readonly string[] s_posCurrencyFormats =
272288
{
@@ -1683,14 +1699,31 @@ internal static unsafe void WriteTwoDigits(byte* ptr, uint value)
16831699
return bufferEnd;
16841700
}
16851701

1702+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
16861703
internal static unsafe string UInt32ToDecStr(uint value)
16871704
{
1688-
// For single-digit values that are very common, especially 0 and 1, just return cached strings.
1689-
if (value < 10)
1705+
// For small numbers, consult a lazily-populated cache.
1706+
if (value < SmallNumberCacheLength)
16901707
{
1691-
return s_singleDigitStringCache[value];
1708+
return UInt32ToDecStrForKnownSmallNumber(value);
16921709
}
16931710

1711+
return UInt32ToDecStr_NoSmallNumberCheck(value);
1712+
}
1713+
1714+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
1715+
internal static string UInt32ToDecStrForKnownSmallNumber(uint value)
1716+
{
1717+
Debug.Assert(value < SmallNumberCacheLength);
1718+
return s_smallNumberCache[value] ?? CreateAndCacheString(value);
1719+
1720+
[MethodImpl(MethodImplOptions.NoInlining)] // keep rare usage out of fast path
1721+
static string CreateAndCacheString(uint value) =>
1722+
s_smallNumberCache[value] = UInt32ToDecStr_NoSmallNumberCheck(value);
1723+
}
1724+
1725+
private static unsafe string UInt32ToDecStr_NoSmallNumberCheck(uint value)
1726+
{
16941727
int bufferLength = FormattingHelpers.CountDigits(value);
16951728

16961729
string result = string.FastAllocateString(bufferLength);
@@ -2086,10 +2119,10 @@ private static uint Int64DivMod1E9(ref ulong value)
20862119

20872120
internal static unsafe string UInt64ToDecStr(ulong value)
20882121
{
2089-
// For single-digit values that are very common, especially 0 and 1, just return cached strings.
2090-
if (value < 10)
2122+
// For small numbers, consult a lazily-populated cache.
2123+
if (value < SmallNumberCacheLength)
20912124
{
2092-
return s_singleDigitStringCache[value];
2125+
return UInt32ToDecStrForKnownSmallNumber((uint)value);
20932126
}
20942127

20952128
int bufferLength = FormattingHelpers.CountDigits(value);
@@ -2374,15 +2407,13 @@ private static ulong Int128DivMod1E19(ref UInt128 value)
23742407

23752408
internal static unsafe string UInt128ToDecStr(UInt128 value)
23762409
{
2377-
// Intrinsified in mono interpreter
2378-
int bufferLength = FormattingHelpers.CountDigits(value);
2379-
2380-
// For single-digit values that are very common, especially 0 and 1, just return cached strings.
2381-
if (bufferLength == 1)
2410+
if (value.Upper == 0)
23822411
{
2383-
return s_singleDigitStringCache[value.Lower];
2412+
return UInt64ToDecStr(value.Lower);
23842413
}
23852414

2415+
int bufferLength = FormattingHelpers.CountDigits(value);
2416+
23862417
string result = string.FastAllocateString(bufferLength);
23872418
fixed (char* buffer = result)
23882419
{

0 commit comments

Comments
 (0)