Skip to content

Commit 5af47bb

Browse files
authored
Increase size of Number's small numbers cache, and make it lazy (#79061)
* Increase size of Number's small numbers cache, and make it lazy * Remove AggressiveInlinings * Remove mono interpreter intrinsic that depend on single number cache
1 parent e33d7ef commit 5af47bb

File tree

6 files changed

+42
-69
lines changed

6 files changed

+42
-69
lines changed

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

Lines changed: 42 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
{
@@ -1685,12 +1701,27 @@ internal static unsafe void WriteTwoDigits(byte* ptr, uint value)
16851701

16861702
internal static unsafe string UInt32ToDecStr(uint value)
16871703
{
1688-
// For single-digit values that are very common, especially 0 and 1, just return cached strings.
1689-
if (value < 10)
1704+
// For small numbers, consult a lazily-populated cache.
1705+
if (value < SmallNumberCacheLength)
16901706
{
1691-
return s_singleDigitStringCache[value];
1707+
return UInt32ToDecStrForKnownSmallNumber(value);
16921708
}
16931709

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

16961727
string result = string.FastAllocateString(bufferLength);
@@ -2086,10 +2117,10 @@ private static uint Int64DivMod1E9(ref ulong value)
20862117

20872118
internal static unsafe string UInt64ToDecStr(ulong value)
20882119
{
2089-
// For single-digit values that are very common, especially 0 and 1, just return cached strings.
2090-
if (value < 10)
2120+
// For small numbers, consult a lazily-populated cache.
2121+
if (value < SmallNumberCacheLength)
20912122
{
2092-
return s_singleDigitStringCache[value];
2123+
return UInt32ToDecStrForKnownSmallNumber((uint)value);
20932124
}
20942125

20952126
int bufferLength = FormattingHelpers.CountDigits(value);
@@ -2374,15 +2405,13 @@ private static ulong Int128DivMod1E19(ref UInt128 value)
23742405

23752406
internal static unsafe string UInt128ToDecStr(UInt128 value)
23762407
{
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)
2408+
if (value.Upper == 0)
23822409
{
2383-
return s_singleDigitStringCache[value.Lower];
2410+
return UInt64ToDecStr(value.Lower);
23842411
}
23852412

2413+
int bufferLength = FormattingHelpers.CountDigits(value);
2414+
23862415
string result = string.FastAllocateString(bufferLength);
23872416
fixed (char* buffer = result)
23882417
{

src/mono/mono/mini/interp/interp-intrins.c

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -104,27 +104,6 @@ interp_intrins_math_divrem (guint32 a, guint32 b, guint32 *result)
104104
return div;
105105
}
106106

107-
MonoString*
108-
interp_intrins_u32_to_decstr (guint32 value, MonoArray *cache, MonoVTable *vtable)
109-
{
110-
// Number.UInt32ToDecStr
111-
int bufferLength = interp_intrins_count_digits (value);
112-
113-
if (bufferLength == 1)
114-
return mono_array_get_fast (cache, MonoString*, value);
115-
116-
int size = (G_STRUCT_OFFSET (MonoString, chars) + (((size_t)bufferLength + 1) * 2));
117-
MonoString* result = mono_gc_alloc_string (vtable, size, bufferLength);
118-
mono_unichar2 *buffer = &result->chars [0];
119-
mono_unichar2 *p = buffer + bufferLength;
120-
do {
121-
guint32 remainder;
122-
value = interp_intrins_math_divrem (value, 10, &remainder);
123-
*(--p) = (mono_unichar2)(remainder + '0');
124-
} while (value != 0);
125-
return result;
126-
}
127-
128107
mono_u
129108
interp_intrins_widen_ascii_to_utf16 (guint8 *pAsciiBuffer, mono_unichar2 *pUtf16Buffer, mono_u elementCount)
130109
{

src/mono/mono/mini/interp/interp-intrins.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ interp_intrins_ordinal_ignore_case_ascii (guint32 valueA, guint32 valueB);
1818
int
1919
interp_intrins_64ordinal_ignore_case_ascii (guint64 valueA, guint64 valueB);
2020

21-
MonoString*
22-
interp_intrins_u32_to_decstr (guint32 value, MonoArray *cache, MonoVTable *vtable);
23-
2421
mono_u
2522
interp_intrins_widen_ascii_to_utf16 (guint8 *pAsciiBuffer, mono_unichar2 *pUtf16Buffer, mono_u elementCount);
2623

src/mono/mono/mini/interp/interp.c

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5761,13 +5761,6 @@ MINT_IN_CASE(MINT_BRTRUE_I8_SP) ZEROP_SP(gint64, !=); MINT_IN_BREAK;
57615761
ip += 4;
57625762
MINT_IN_BREAK;
57635763
}
5764-
MINT_IN_CASE(MINT_INTRINS_U32_TO_DECSTR) {
5765-
MonoArray **cache_addr = (MonoArray**)frame->imethod->data_items [ip [3]];
5766-
MonoVTable *string_vtable = (MonoVTable*)frame->imethod->data_items [ip [4]];
5767-
LOCAL_VAR (ip [1], MonoObject*) = (MonoObject*)interp_intrins_u32_to_decstr (LOCAL_VAR (ip [2], guint32), *cache_addr, string_vtable);
5768-
ip += 5;
5769-
MINT_IN_BREAK;
5770-
}
57715764
MINT_IN_CASE(MINT_INTRINS_WIDEN_ASCII_TO_UTF16) {
57725765
LOCAL_VAR (ip [1], mono_u) = interp_intrins_widen_ascii_to_utf16 (LOCAL_VAR (ip [2], guint8*), LOCAL_VAR (ip [3], mono_unichar2*), LOCAL_VAR (ip [4], mono_u));
57735766
ip += 5;

src/mono/mono/mini/interp/mintops.def

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,6 @@ OPDEF(MINT_INTRINS_ASCII_CHARS_TO_UPPERCASE, "intrins_ascii_chars_to_uppercase",
802802
OPDEF(MINT_INTRINS_MEMORYMARSHAL_GETARRAYDATAREF, "intrins_memorymarshal_getarraydataref", 3, 1, 1, MintOpNoArgs)
803803
OPDEF(MINT_INTRINS_ORDINAL_IGNORE_CASE_ASCII, "intrins_ordinal_ignore_case_ascii", 4, 1, 2, MintOpNoArgs)
804804
OPDEF(MINT_INTRINS_64ORDINAL_IGNORE_CASE_ASCII, "intrins_64ordinal_ignore_case_ascii", 4, 1, 2, MintOpNoArgs)
805-
OPDEF(MINT_INTRINS_U32_TO_DECSTR, "intrins_u32_to_decstr", 5, 1, 1, MintOpTwoShorts)
806805
OPDEF(MINT_INTRINS_WIDEN_ASCII_TO_UTF16, "intrins_widen_ascii_to_utf16", 5, 1, 3, MintOpNoArgs)
807806

808807
OPDEF(MINT_METADATA_UPDATE_LDFLDA, "metadata_update.ldflda", 5, 1, 1, MintOpTwoShorts)

src/mono/mono/mini/interp/transform.c

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2056,30 +2056,6 @@ interp_handle_intrinsics (TransformData *td, MonoMethod *target_method, MonoClas
20562056
} else if (in_corlib && !strcmp (klass_name_space, "System.Text") && !strcmp (klass_name, "ASCIIUtility")) {
20572057
if (!strcmp (tm, "WidenAsciiToUtf16"))
20582058
*op = MINT_INTRINS_WIDEN_ASCII_TO_UTF16;
2059-
} else if (in_corlib && !strcmp (klass_name_space, "System") && !strcmp (klass_name, "Number")) {
2060-
if (!strcmp (tm, "UInt32ToDecStr") && csignature->param_count == 1) {
2061-
ERROR_DECL(error);
2062-
MonoVTable *vtable = mono_class_vtable_checked (target_method->klass, error);
2063-
if (!is_ok (error)) {
2064-
mono_interp_error_cleanup (error);
2065-
return FALSE;
2066-
}
2067-
/* Don't use intrinsic if cctor not yet run */
2068-
if (!vtable->initialized)
2069-
return FALSE;
2070-
/* The cache is the first static field. Update this if bcl code changes */
2071-
MonoClassField *field = m_class_get_fields (target_method->klass);
2072-
g_assert (!strcmp (field->name, "s_singleDigitStringCache"));
2073-
interp_add_ins (td, MINT_INTRINS_U32_TO_DECSTR);
2074-
td->last_ins->data [0] = get_data_item_index (td, mono_static_field_get_addr (vtable, field));
2075-
td->last_ins->data [1] = get_data_item_index (td, mono_class_vtable_checked (mono_defaults.string_class, error));
2076-
td->sp--;
2077-
interp_ins_set_sreg (td->last_ins, td->sp [0].local);
2078-
push_type (td, STACK_TYPE_O, mono_defaults.string_class);
2079-
interp_ins_set_dreg (td->last_ins, td->sp [-1].local);
2080-
td->ip += 5;
2081-
return TRUE;
2082-
}
20832059
} else if (in_corlib && !strcmp (klass_name_space, "System") &&
20842060
(!strcmp (klass_name, "Math") || !strcmp (klass_name, "MathF"))) {
20852061
gboolean is_float = strcmp (klass_name, "MathF") == 0;

0 commit comments

Comments
 (0)