From 86958ab65852211f0372c9cb47b23db555db36e2 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 23 Aug 2025 21:42:32 +0300 Subject: [PATCH 01/23] benchmark --- benchmarks/CMakeLists.txt | 1 + benchmarks/src/integer_to_string.cpp | 84 ++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 benchmarks/src/integer_to_string.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index caab5625c60..3231a81e17f 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -112,6 +112,7 @@ add_benchmark(find_first_of src/find_first_of.cpp) add_benchmark(has_single_bit src/has_single_bit.cpp) add_benchmark(includes src/includes.cpp) add_benchmark(iota src/iota.cpp) +add_benchmark(integer_to_string src/integer_to_string.cpp) add_benchmark(is_sorted_until src/is_sorted_until.cpp) add_benchmark(locale_classic src/locale_classic.cpp) add_benchmark(locate_zone src/locate_zone.cpp) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp new file mode 100644 index 00000000000..569dada1914 --- /dev/null +++ b/benchmarks/src/integer_to_string.cpp @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +template +auto generate_array() { + array a; + + mt19937 gen; + lognormal_distribution dis(M, S); + ranges::generate(a, [&] { return static_cast(dis(gen)); }); + + if constexpr (is_signed_v) { + bernoulli_distribution b(0.5); + ranges::for_each(a, [&](T& v) { v *= (b(gen) ? -1 : 1); }); + } + + return a; +} + +template +void internal_integer_to_buff(benchmark::State& state) { + auto a = generate_array(); + + char buff[24]; + + auto it = a.begin(); + for (auto _ : state) { + auto i = *it; + benchmark::DoNotOptimize(i); + auto s = std::_UIntegral_to_buff(buff, i); + benchmark::DoNotOptimize(s); + + ++it; + if (it == a.end()) { + it = a.begin(); + } + } +} + +template +void integer_to_string(benchmark::State& state) { + auto a = generate_array(); + + auto it = a.begin(); + for (auto _ : state) { + auto i = *it; + benchmark::DoNotOptimize(i); + auto s = to_string(i); + benchmark::DoNotOptimize(s); + + ++it; + if (it == a.end()) { + it = a.begin(); + } + } +} + +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); + +BENCHMARK(integer_to_string); +BENCHMARK(integer_to_string); +BENCHMARK(integer_to_string); +BENCHMARK(integer_to_string); + +BENCHMARK(integer_to_string); +BENCHMARK(integer_to_string); +BENCHMARK(integer_to_string); +BENCHMARK(integer_to_string); + +BENCHMARK_MAIN(); From 8b6f8637f1aadf3aa27a4c2290d642fd8e8cdfff Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 23 Aug 2025 21:52:26 +0300 Subject: [PATCH 02/23] 100 branch in to_string impl --- stl/inc/xcharconv_ryu_tables.h | 20 ++++++++------------ stl/inc/xmemory | 10 ++++++++++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/stl/inc/xcharconv_ryu_tables.h b/stl/inc/xcharconv_ryu_tables.h index cbda03e8b05..43b19ccbb62 100644 --- a/stl/inc/xcharconv_ryu_tables.h +++ b/stl/inc/xcharconv_ryu_tables.h @@ -37,10 +37,6 @@ #include #if _STL_COMPILER_PREPROCESSOR -#if !_HAS_CXX17 -#error The contents of are only available with C++17. (Also, you should not include this internal header.) -#endif // !_HAS_CXX17 - #include #pragma pack(push, _CRT_PACKING) @@ -64,7 +60,7 @@ _STD_BEGIN // generation by copying pairs of digits into the final output. template constexpr _CharT __DIGIT_TABLE[] = {_CharT{}}; -template <> inline constexpr char __DIGIT_TABLE[200] = { +template <> _INLINE_VAR constexpr char __DIGIT_TABLE[200] = { '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', @@ -77,7 +73,7 @@ template <> inline constexpr char __DIGIT_TABLE[200] = { '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' }; -template <> inline constexpr wchar_t __DIGIT_TABLE[200] = { +template <> _INLINE_VAR constexpr wchar_t __DIGIT_TABLE[200] = { L'0',L'0',L'0',L'1',L'0',L'2',L'0',L'3',L'0',L'4',L'0',L'5',L'0',L'6',L'0',L'7',L'0',L'8',L'0',L'9', L'1',L'0',L'1',L'1',L'1',L'2',L'1',L'3',L'1',L'4',L'1',L'5',L'1',L'6',L'1',L'7',L'1',L'8',L'1',L'9', L'2',L'0',L'2',L'1',L'2',L'2',L'2',L'3',L'2',L'4',L'2',L'5',L'2',L'6',L'2',L'7',L'2',L'8',L'2',L'9', @@ -103,9 +99,9 @@ extern const uint64_t __DOUBLE_POW5_SPLIT[326][2]; // vvvvvvvvvv DERIVED FROM d2fixed_full_table.h vvvvvvvvvv -inline constexpr int __TABLE_SIZE = 64; +_INLINE_VAR constexpr int __TABLE_SIZE = 64; -inline constexpr uint16_t __POW10_OFFSET[__TABLE_SIZE] = { +_INLINE_VAR constexpr uint16_t __POW10_OFFSET[__TABLE_SIZE] = { 0, 2, 5, 8, 12, 16, 21, 26, 32, 39, 46, 54, 62, 71, 80, 90, 100, 111, 122, 134, 146, 159, 173, 187, 202, 217, 233, 249, 266, 283, @@ -117,10 +113,10 @@ inline constexpr uint16_t __POW10_OFFSET[__TABLE_SIZE] = { extern const uint64_t __POW10_SPLIT[1224][3]; -inline constexpr int __TABLE_SIZE_2 = 69; -inline constexpr int __ADDITIONAL_BITS_2 = 120; +_INLINE_VAR constexpr int __TABLE_SIZE_2 = 69; +_INLINE_VAR constexpr int __ADDITIONAL_BITS_2 = 120; -inline constexpr uint16_t __POW10_OFFSET_2[__TABLE_SIZE_2] = { +_INLINE_VAR constexpr uint16_t __POW10_OFFSET_2[__TABLE_SIZE_2] = { 0, 2, 6, 12, 20, 29, 40, 52, 66, 80, 95, 112, 130, 150, 170, 192, 215, 240, 265, 292, 320, 350, 381, 413, 446, 480, 516, 552, 590, 629, @@ -130,7 +126,7 @@ inline constexpr uint16_t __POW10_OFFSET_2[__TABLE_SIZE_2] = { 2465, 2544, 2625, 2706, 2789, 2873, 2959, 3046, 3133 }; -inline constexpr uint8_t __MIN_BLOCK_2[__TABLE_SIZE_2] = { +_INLINE_VAR constexpr uint8_t __MIN_BLOCK_2[__TABLE_SIZE_2] = { 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 12, 13, 13, diff --git a/stl/inc/xmemory b/stl/inc/xmemory index e4cfa3008b5..c2cdccf3823 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -12,6 +12,7 @@ #include // TRANSITION, see GH-4634: Lots of user code assumes that drags in #include #include +#include #include #if _HAS_CXX20 @@ -2786,6 +2787,15 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ + if constexpr (_Is_any_of_v<_Elem, char, wchar_t>) { + while (_UVal_trunc >= 100) { + const unsigned long _UVal_trunc_part = _UVal_trunc % 100; + _UVal_trunc /= 100; + *--_RNext = static_cast<_Elem>(__DIGIT_TABLE<_Elem>[_UVal_trunc_part * 2 + 1]); + *--_RNext = static_cast<_Elem>(__DIGIT_TABLE<_Elem>[_UVal_trunc_part * 2]); + } + } + do { *--_RNext = static_cast<_Elem>('0' + _UVal_trunc % 10); _UVal_trunc /= 10; From 84b3d1aae54d59dc1e979782c2b8eaff616835e7 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 23 Aug 2025 22:42:17 +0300 Subject: [PATCH 03/23] sort better --- benchmarks/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 3231a81e17f..7ae6b158397 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -111,8 +111,8 @@ add_benchmark(find_and_count src/find_and_count.cpp) add_benchmark(find_first_of src/find_first_of.cpp) add_benchmark(has_single_bit src/has_single_bit.cpp) add_benchmark(includes src/includes.cpp) -add_benchmark(iota src/iota.cpp) add_benchmark(integer_to_string src/integer_to_string.cpp) +add_benchmark(iota src/iota.cpp) add_benchmark(is_sorted_until src/is_sorted_until.cpp) add_benchmark(locale_classic src/locale_classic.cpp) add_benchmark(locate_zone src/locate_zone.cpp) From 08b83da7c7c2a5d34144a15e05e9241b1c64b114 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 24 Aug 2025 14:44:26 +0300 Subject: [PATCH 04/23] hack around some linker issue in C++14 --- stl/inc/xcharconv_ryu_tables.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/stl/inc/xcharconv_ryu_tables.h b/stl/inc/xcharconv_ryu_tables.h index 43b19ccbb62..15ba18f72a7 100644 --- a/stl/inc/xcharconv_ryu_tables.h +++ b/stl/inc/xcharconv_ryu_tables.h @@ -56,6 +56,10 @@ _STD_BEGIN // vvvvvvvvvv DERIVED FROM digit_table.h vvvvvvvvvv +#if !_HAS_CXX17 +namespace { // work around Clang link issues +#endif // !_HAS_CXX17 + // A table of all two-digit numbers. This is used to speed up decimal digit // generation by copying pairs of digits into the final output. template constexpr _CharT __DIGIT_TABLE[] = {_CharT{}}; @@ -86,6 +90,10 @@ template <> _INLINE_VAR constexpr wchar_t __DIGIT_TABLE[200] = { L'9',L'0',L'9',L'1',L'9',L'2',L'9',L'3',L'9',L'4',L'9',L'5',L'9',L'6',L'9',L'7',L'9',L'8',L'9',L'9' }; +#if !_HAS_CXX17 +} // unnamed namespace +#endif // !_HAS_CXX17 + // ^^^^^^^^^^ DERIVED FROM digit_table.h ^^^^^^^^^^ // vvvvvvvvvv DERIVED FROM d2s_full_table.h vvvvvvvvvv From 1ffa3387ead99e70bfa32b692a4cbdba3cc5cbdb Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 28 Nov 2025 19:48:18 +0200 Subject: [PATCH 05/23] unshare table --- stl/inc/xcharconv_ryu_tables.h | 30 +++++++++++++----------------- stl/inc/xmemory | 25 +++++++++++++++++-------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/stl/inc/xcharconv_ryu_tables.h b/stl/inc/xcharconv_ryu_tables.h index 9f405d33af4..cbda03e8b05 100644 --- a/stl/inc/xcharconv_ryu_tables.h +++ b/stl/inc/xcharconv_ryu_tables.h @@ -37,6 +37,10 @@ #include #if _STL_COMPILER_PREPROCESSOR +#if !_HAS_CXX17 +#error The contents of are only available with C++17. (Also, you should not include this internal header.) +#endif // !_HAS_CXX17 + #include #pragma pack(push, _CRT_PACKING) @@ -52,19 +56,15 @@ _STD_BEGIN // See xcharconv_ryu.h for the exact commit. // (Keep the cgmanifest.json commitHash in sync.) -// clang-format off: avoid diverging from external code +// clang-format off // vvvvvvvvvv DERIVED FROM digit_table.h vvvvvvvvvv -#if !_HAS_CXX17 -namespace { // work around Clang link issues -#endif // !_HAS_CXX17 - // A table of all two-digit numbers. This is used to speed up decimal digit // generation by copying pairs of digits into the final output. template constexpr _CharT __DIGIT_TABLE[] = {_CharT{}}; -template <> _INLINE_VAR constexpr char __DIGIT_TABLE[200] = { +template <> inline constexpr char __DIGIT_TABLE[200] = { '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9', '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9', '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9', @@ -77,7 +77,7 @@ template <> _INLINE_VAR constexpr char __DIGIT_TABLE[200] = { '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9' }; -template <> _INLINE_VAR constexpr wchar_t __DIGIT_TABLE[200] = { +template <> inline constexpr wchar_t __DIGIT_TABLE[200] = { L'0',L'0',L'0',L'1',L'0',L'2',L'0',L'3',L'0',L'4',L'0',L'5',L'0',L'6',L'0',L'7',L'0',L'8',L'0',L'9', L'1',L'0',L'1',L'1',L'1',L'2',L'1',L'3',L'1',L'4',L'1',L'5',L'1',L'6',L'1',L'7',L'1',L'8',L'1',L'9', L'2',L'0',L'2',L'1',L'2',L'2',L'2',L'3',L'2',L'4',L'2',L'5',L'2',L'6',L'2',L'7',L'2',L'8',L'2',L'9', @@ -90,10 +90,6 @@ template <> _INLINE_VAR constexpr wchar_t __DIGIT_TABLE[200] = { L'9',L'0',L'9',L'1',L'9',L'2',L'9',L'3',L'9',L'4',L'9',L'5',L'9',L'6',L'9',L'7',L'9',L'8',L'9',L'9' }; -#if !_HAS_CXX17 -} // unnamed namespace -#endif // !_HAS_CXX17 - // ^^^^^^^^^^ DERIVED FROM digit_table.h ^^^^^^^^^^ // vvvvvvvvvv DERIVED FROM d2s_full_table.h vvvvvvvvvv @@ -107,9 +103,9 @@ extern const uint64_t __DOUBLE_POW5_SPLIT[326][2]; // vvvvvvvvvv DERIVED FROM d2fixed_full_table.h vvvvvvvvvv -_INLINE_VAR constexpr int __TABLE_SIZE = 64; +inline constexpr int __TABLE_SIZE = 64; -_INLINE_VAR constexpr uint16_t __POW10_OFFSET[__TABLE_SIZE] = { +inline constexpr uint16_t __POW10_OFFSET[__TABLE_SIZE] = { 0, 2, 5, 8, 12, 16, 21, 26, 32, 39, 46, 54, 62, 71, 80, 90, 100, 111, 122, 134, 146, 159, 173, 187, 202, 217, 233, 249, 266, 283, @@ -121,10 +117,10 @@ _INLINE_VAR constexpr uint16_t __POW10_OFFSET[__TABLE_SIZE] = { extern const uint64_t __POW10_SPLIT[1224][3]; -_INLINE_VAR constexpr int __TABLE_SIZE_2 = 69; -_INLINE_VAR constexpr int __ADDITIONAL_BITS_2 = 120; +inline constexpr int __TABLE_SIZE_2 = 69; +inline constexpr int __ADDITIONAL_BITS_2 = 120; -_INLINE_VAR constexpr uint16_t __POW10_OFFSET_2[__TABLE_SIZE_2] = { +inline constexpr uint16_t __POW10_OFFSET_2[__TABLE_SIZE_2] = { 0, 2, 6, 12, 20, 29, 40, 52, 66, 80, 95, 112, 130, 150, 170, 192, 215, 240, 265, 292, 320, 350, 381, 413, 446, 480, 516, 552, 590, 629, @@ -134,7 +130,7 @@ _INLINE_VAR constexpr uint16_t __POW10_OFFSET_2[__TABLE_SIZE_2] = { 2465, 2544, 2625, 2706, 2789, 2873, 2959, 3046, 3133 }; -_INLINE_VAR constexpr uint8_t __MIN_BLOCK_2[__TABLE_SIZE_2] = { +inline constexpr uint8_t __MIN_BLOCK_2[__TABLE_SIZE_2] = { 0, 0, 0, 0, 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 12, 13, 13, diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 9e616ba9543..8e78861c5cc 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -12,7 +12,6 @@ #include // TRANSITION, see GH-4634: Lots of user code assumes that drags in #include #include -#include #include #if _HAS_CXX20 @@ -2787,13 +2786,23 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ - if constexpr (_Is_any_of_v<_Elem, char, wchar_t>) { - while (_UVal_trunc >= 100) { - const unsigned long _UVal_trunc_part = _UVal_trunc % 100; - _UVal_trunc /= 100; - *--_RNext = static_cast<_Elem>(__DIGIT_TABLE<_Elem>[_UVal_trunc_part * 2 + 1]); - *--_RNext = static_cast<_Elem>(__DIGIT_TABLE<_Elem>[_UVal_trunc_part * 2]); - } + static constexpr _Elem _Digits_table[] = { // + '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', // + '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', // + '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', // + '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', // + '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', // + '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', // + '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', // + '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', // + '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', // + '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'}; + + while (_UVal_trunc >= 100) { + const unsigned long _UVal_trunc_part = _UVal_trunc % 100; + _UVal_trunc /= 100; + _RNext -= 2; + _CSTD memcpy(_RNext, _Digits_table + _UVal_trunc_part * 2, 2); } do { From f422b45d1283e16268c656b6736ebe271eb5b144 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 28 Nov 2025 19:49:44 +0200 Subject: [PATCH 06/23] unrevert merge --- stl/inc/xcharconv_ryu_tables.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/xcharconv_ryu_tables.h b/stl/inc/xcharconv_ryu_tables.h index cbda03e8b05..ad312ce2a0b 100644 --- a/stl/inc/xcharconv_ryu_tables.h +++ b/stl/inc/xcharconv_ryu_tables.h @@ -56,7 +56,7 @@ _STD_BEGIN // See xcharconv_ryu.h for the exact commit. // (Keep the cgmanifest.json commitHash in sync.) -// clang-format off +// clang-format off: avoid diverging from external code // vvvvvvvvvv DERIVED FROM digit_table.h vvvvvvvvvv From b39a620aa2cdc146eab452a1bb4bb83ca7ceb657 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 28 Nov 2025 19:56:47 +0200 Subject: [PATCH 07/23] format --- stl/inc/xmemory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 8e78861c5cc..4a5b6df0d87 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2786,7 +2786,7 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ - static constexpr _Elem _Digits_table[] = { // + static constexpr _Elem _Digits_table[] = {// '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', // '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', // '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', // From 77d37fff0b846afae82a1222d95d4603e77ccd8d Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 28 Nov 2025 20:04:01 +0200 Subject: [PATCH 08/23] eliminate the tail loop, we need at most one iteration --- stl/inc/xmemory | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 4a5b6df0d87..ab71ab60b3d 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2798,17 +2798,20 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', // '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'}; - while (_UVal_trunc >= 100) { - const unsigned long _UVal_trunc_part = _UVal_trunc % 100; - _UVal_trunc /= 100; - _RNext -= 2; - _CSTD memcpy(_RNext, _Digits_table + _UVal_trunc_part * 2, 2); + if (_UVal_trunc >= 10) { + do { + const unsigned long _UVal_trunc_part = _UVal_trunc % 100; + _UVal_trunc /= 100; + _RNext -= 2; + _CSTD memcpy(_RNext, _Digits_table + _UVal_trunc_part * 2, 2); + } while (_UVal_trunc >= 10); + + if (_UVal_trunc == 0) { + return _RNext; + } } - do { - *--_RNext = static_cast<_Elem>('0' + _UVal_trunc % 10); - _UVal_trunc /= 10; - } while (_UVal_trunc != 0); + *--_RNext = static_cast<_Elem>('0' + _UVal_trunc); return _RNext; } _STD_END From c1efce4d0c7df569608b9fef89b50e12a1191bae Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 28 Nov 2025 20:14:22 +0200 Subject: [PATCH 09/23] size --- stl/inc/xmemory | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index ab71ab60b3d..2ab38cd59c6 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2803,7 +2803,7 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot const unsigned long _UVal_trunc_part = _UVal_trunc % 100; _UVal_trunc /= 100; _RNext -= 2; - _CSTD memcpy(_RNext, _Digits_table + _UVal_trunc_part * 2, 2); + _CSTD memcpy(_RNext, _Digits_table + _UVal_trunc_part * 2, 2 * sizeof(_Elem)); } while (_UVal_trunc >= 10); if (_UVal_trunc == 0) { From ed71c09368760e8e3465dff2923bd6415205918e Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 28 Nov 2025 23:59:36 +0200 Subject: [PATCH 10/23] Generate table --- stl/inc/xmemory | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 2ab38cd59c6..6606d8531c7 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2762,6 +2762,20 @@ namespace ranges { } // namespace ranges #endif // _HAS_CXX23 +template +constexpr auto _UIntegral_to_buff_digits() { + struct { + _Elem _Data[200]; + } _Result = {}; + + for (int _Ix = 0; _Ix != 100; ++_Ix) { + _Result._Data[_Ix * 2 + 0] = static_cast<_Elem>(_Ix / 10 + '0'); + _Result._Data[_Ix * 2 + 1] = static_cast<_Elem>(_Ix % 10 + '0'); + } + + return _Result; +} + template _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by both to_string and thread::id output // format _UVal into buffer *ending at* _RNext @@ -2785,25 +2799,14 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ - - static constexpr _Elem _Digits_table[] = {// - '0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9', // - '1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9', // - '2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9', // - '3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9', // - '4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9', // - '5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9', // - '6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9', // - '7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9', // - '8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9', // - '9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'}; + static constexpr auto _Table = _STD _UIntegral_to_buff_digits<_Elem>(); if (_UVal_trunc >= 10) { do { const unsigned long _UVal_trunc_part = _UVal_trunc % 100; _UVal_trunc /= 100; _RNext -= 2; - _CSTD memcpy(_RNext, _Digits_table + _UVal_trunc_part * 2, 2 * sizeof(_Elem)); + _CSTD memcpy(_RNext, _Table._Data + _UVal_trunc_part * 2, 2 * sizeof(_Elem)); } while (_UVal_trunc >= 10); if (_UVal_trunc == 0) { From 9f3e9bcd24955d6006b250a67dc847d8b1db8408 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 29 Nov 2025 13:02:30 +0200 Subject: [PATCH 11/23] Let's consistently change 32-bit path --- stl/inc/xmemory | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 6606d8531c7..12c4e5c0960 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2768,9 +2768,9 @@ constexpr auto _UIntegral_to_buff_digits() { _Elem _Data[200]; } _Result = {}; - for (int _Ix = 0; _Ix != 100; ++_Ix) { - _Result._Data[_Ix * 2 + 0] = static_cast<_Elem>(_Ix / 10 + '0'); - _Result._Data[_Ix * 2 + 1] = static_cast<_Elem>(_Ix % 10 + '0'); + for (int _Idx = 0; _Idx != 100; ++_Idx) { + _Result._Data[_Idx * 2 + 0] = static_cast<_Elem>(_Idx / 10 + '0'); + _Result._Data[_Idx * 2 + 1] = static_cast<_Elem>(_Idx % 10 + '0'); } return _Result; @@ -2780,6 +2780,7 @@ template _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by both to_string and thread::id output // format _UVal into buffer *ending at* _RNext static_assert(is_unsigned_v<_UTy>, "_UTy must be unsigned"); + static constexpr auto _Table = _STD _UIntegral_to_buff_digits<_Elem>(); #ifdef _WIN64 auto _UVal_trunc = _UVal; @@ -2790,16 +2791,19 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_chunk = static_cast(_UVal % 1000000000); _UVal /= 1000000000; - for (int _Idx = 0; _Idx != 9; ++_Idx) { - *--_RNext = static_cast<_Elem>('0' + _UVal_chunk % 10); - _UVal_chunk /= 10; + for (int _Idx = 0; _Idx != 4; ++_Idx) { + const unsigned long _UVal_chunk_part = _UVal_chunk % 100; + _UVal_chunk /= 100; + _RNext -= 2; + _CSTD memcpy(_RNext, _Table._Data + _UVal_chunk_part * 2, 2 * sizeof(_Elem)); } + + *--_RNext = static_cast<_Elem>('0' + _UVal_chunk); } } auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ - static constexpr auto _Table = _STD _UIntegral_to_buff_digits<_Elem>(); if (_UVal_trunc >= 10) { do { From edc204231e5a2255dc0c40979f51659f9c618b43 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 04:25:53 -0800 Subject: [PATCH 12/23] Use a multi-dim array with a constructor. --- stl/inc/xmemory | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 90102ee95f7..d6e5a639523 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2763,24 +2763,27 @@ namespace ranges { #endif // _HAS_CXX23 template -constexpr auto _UIntegral_to_buff_digits() { - struct { - _Elem _Data[200]; - } _Result = {}; +struct _Digit_pair_table { + _Elem _Data[100][2]; - for (int _Idx = 0; _Idx != 100; ++_Idx) { - _Result._Data[_Idx * 2 + 0] = static_cast<_Elem>(_Idx / 10 + '0'); - _Result._Data[_Idx * 2 + 1] = static_cast<_Elem>(_Idx % 10 + '0'); + constexpr explicit _Digit_pair_table() : _Data{} { + for (int _Idx = 0; _Idx != 100; ++_Idx) { + _Data[_Idx][0] = static_cast<_Elem>('0' + _Idx / 10); + _Data[_Idx][1] = static_cast<_Elem>('0' + _Idx % 10); + } } - return _Result; -} + _Digit_pair_table(const _Digit_pair_table&) = delete; + _Digit_pair_table& operator=(const _Digit_pair_table&) = delete; +}; + +template +constexpr _Digit_pair_table<_Elem> _Digit_pairs{}; template _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by both to_string and thread::id output // format _UVal into buffer *ending at* _RNext static_assert(is_unsigned_v<_UTy>, "_UTy must be unsigned"); - static constexpr auto _Table = _STD _UIntegral_to_buff_digits<_Elem>(); #ifdef _WIN64 auto _UVal_trunc = _UVal; @@ -2795,7 +2798,7 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot const unsigned long _UVal_chunk_part = _UVal_chunk % 100; _UVal_chunk /= 100; _RNext -= 2; - _CSTD memcpy(_RNext, _Table._Data + _UVal_chunk_part * 2, 2 * sizeof(_Elem)); + _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_chunk_part], 2 * sizeof(_Elem)); } *--_RNext = static_cast<_Elem>('0' + _UVal_chunk); @@ -2810,7 +2813,7 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot const unsigned long _UVal_trunc_part = _UVal_trunc % 100; _UVal_trunc /= 100; _RNext -= 2; - _CSTD memcpy(_RNext, _Table._Data + _UVal_trunc_part * 2, 2 * sizeof(_Elem)); + _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_trunc_part], 2 * sizeof(_Elem)); } while (_UVal_trunc >= 10); if (_UVal_trunc == 0) { From 3708bbff4699431aca5f20cf1df2750cae2dadbb Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 05:15:47 -0800 Subject: [PATCH 13/23] Transform control flow to be simpler. No additional branches. --- stl/inc/xmemory | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index d6e5a639523..b7204c90924 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2808,20 +2808,25 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ - if (_UVal_trunc >= 10) { - do { - const unsigned long _UVal_trunc_part = _UVal_trunc % 100; - _UVal_trunc /= 100; - _RNext -= 2; - _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_trunc_part], 2 * sizeof(_Elem)); - } while (_UVal_trunc >= 10); - - if (_UVal_trunc == 0) { - return _RNext; - } + // If we have a single digit, print [0, 9] and return. (This is necessary to correctly handle 0.) + if (_UVal_trunc < 10) { + *--_RNext = static_cast<_Elem>('0' + _UVal_trunc); + return _RNext; + } + + // Print one or more pairs of digits. + do { + const unsigned long _UVal_trunc_part = _UVal_trunc % 100; + _UVal_trunc /= 100; + _RNext -= 2; + _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_trunc_part], 2 * sizeof(_Elem)); + } while (_UVal_trunc >= 10); + + // If we have an unpaired digit, print it. For example, 1729 is printed as 17 29, and 19937 is printed as 1 99 37. + if (_UVal_trunc != 0) { + *--_RNext = static_cast<_Elem>('0' + _UVal_trunc); } - *--_RNext = static_cast<_Elem>('0' + _UVal_trunc); return _RNext; } _STD_END From 6c2927344e3040caf07aaaac48f893e500dc8b06 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 05:29:07 -0800 Subject: [PATCH 14/23] Use mt19937_64. --- benchmarks/src/integer_to_string.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index 569dada1914..3734f0e1153 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -16,7 +16,7 @@ template auto generate_array() { array a; - mt19937 gen; + mt19937_64 gen; lognormal_distribution dis(M, S); ranges::generate(a, [&] { return static_cast(dis(gen)); }); From 333e3f6775ec94c3e3ac53e4f2617d3e4e27368d Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 05:38:24 -0800 Subject: [PATCH 15/23] Use 20 chars, comment why. --- benchmarks/src/integer_to_string.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index 3734f0e1153..e527f6116f2 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -32,7 +32,7 @@ template void internal_integer_to_buff(benchmark::State& state) { auto a = generate_array(); - char buff[24]; + char buff[20]; // can hold -2^63 and 2^64 - 1 auto it = a.begin(); for (auto _ : state) { From 395c8f6218252370713b936c50405b2b69fcf345 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 05:45:35 -0800 Subject: [PATCH 16/23] Fix major bug: `_UIntegral_to_buff` takes the END of the buffer. --- benchmarks/src/integer_to_string.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index e527f6116f2..537a25d4993 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -33,12 +33,13 @@ void internal_integer_to_buff(benchmark::State& state) { auto a = generate_array(); char buff[20]; // can hold -2^63 and 2^64 - 1 + auto buff_end = end(buff); auto it = a.begin(); for (auto _ : state) { auto i = *it; benchmark::DoNotOptimize(i); - auto s = std::_UIntegral_to_buff(buff, i); + auto s = std::_UIntegral_to_buff(buff_end, i); benchmark::DoNotOptimize(s); ++it; From 65138c1f8a3766cd697e9ac7a71d41e55a6144e5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 05:48:19 -0800 Subject: [PATCH 17/23] Adjust header inclusions. --- benchmarks/src/integer_to_string.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index 537a25d4993..bbcf4095133 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -4,10 +4,9 @@ #include #include #include -#include #include -#include #include +#include #include using namespace std; From 33fef868f1f255a440b554145f1c8b5ff3cb0891 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 05:55:12 -0800 Subject: [PATCH 18/23] Benchmark wchar_t. --- benchmarks/src/integer_to_string.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index bbcf4095133..f98344a8422 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -27,11 +27,11 @@ auto generate_array() { return a; } -template +template void internal_integer_to_buff(benchmark::State& state) { auto a = generate_array(); - char buff[20]; // can hold -2^63 and 2^64 - 1 + CharT buff[20]; // can hold -2^63 and 2^64 - 1 auto buff_end = end(buff); auto it = a.begin(); @@ -66,10 +66,15 @@ void integer_to_string(benchmark::State& state) { } } -BENCHMARK(internal_integer_to_buff); -BENCHMARK(internal_integer_to_buff); -BENCHMARK(internal_integer_to_buff); -BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); + +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); +BENCHMARK(internal_integer_to_buff); BENCHMARK(integer_to_string); BENCHMARK(integer_to_string); From 4b706e529602cf2a15ee9dfcab82387037d15aa5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 06:37:16 -0800 Subject: [PATCH 19/23] Add additional correctness tests for every length. --- .../tests/Dev11_0835323_to_string/test.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/std/tests/Dev11_0835323_to_string/test.cpp b/tests/std/tests/Dev11_0835323_to_string/test.cpp index 10d52236e79..fa270b4cfa0 100644 --- a/tests/std/tests/Dev11_0835323_to_string/test.cpp +++ b/tests/std/tests/Dev11_0835323_to_string/test.cpp @@ -144,6 +144,34 @@ int main() { L"2304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000"); assert(to_wstring(numeric_limits::infinity()) == L"inf"); + // Exercise the logic for printing digit pairs, and the optimization for printing 64-bit values on 32-bit targets. + assert(to_string(0ULL) == "0"); + assert(to_string(1ULL) == "1"); + assert(to_string(12ULL) == "12"); + assert(to_string(123ULL) == "123"); + assert(to_string(1234ULL) == "1234"); + assert(to_string(12345ULL) == "12345"); + assert(to_string(123456ULL) == "123456"); + assert(to_string(1234567ULL) == "1234567"); + assert(to_string(12345678ULL) == "12345678"); + assert(to_string(123456789ULL) == "123456789"); + assert(to_string(1234567890ULL) == "1234567890"); + assert(to_string(4294967295ULL) == "4294967295"); // 2^32-1 + assert(to_string(4294967296ULL) == "4294967296"); + assert(to_string(12345678901ULL) == "12345678901"); + assert(to_string(123456789012ULL) == "123456789012"); + assert(to_string(1234567890123ULL) == "1234567890123"); + assert(to_string(12345678901234ULL) == "12345678901234"); + assert(to_string(123456789012345ULL) == "123456789012345"); + assert(to_string(1234567890123456ULL) == "1234567890123456"); + assert(to_string(12345678901234567ULL) == "12345678901234567"); + assert(to_string(123456789012345678ULL) == "123456789012345678"); + assert(to_string(429496729599999999ULL) == "429496729599999999"); // 2^32-1 followed by 8 digits + assert(to_string(429496729600000000ULL) == "429496729600000000"); + assert(to_string(1234567890123456789ULL) == "1234567890123456789"); + assert(to_string(4294967295999999999ULL) == "4294967295999999999"); // 2^32-1 followed by 9 digits + assert(to_string(4294967296000000000ULL) == "4294967296000000000"); + assert(to_string(12345678901234567890ULL) == "12345678901234567890"); // Also test DevDiv-875295 ": std::stof returns 1.#INF instead of throwing out_of_range [libcxx]". From 5bf3370ec5d0dd47640f93816e6167f549e0f6f1 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 06:09:46 -0800 Subject: [PATCH 20/23] For x86, print 8 digits at a time. The benchmark shows a minor speedup: Benchmark | 9 digits | 8 digits | Speedup for 8 digits ----------------------------------------------------------|----------|----------|--------------------- `internal_integer_to_buff` | 9.46 ns | 8.47 ns | 1.12 `internal_integer_to_buff` | 8.40 ns | 8.13 ns | 1.03 `integer_to_string` | 19.7 ns | 18.4 ns | 1.07 `integer_to_string` | 20.9 ns | 19.6 ns | 1.07 --- stl/inc/xmemory | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index b7204c90924..17267e2733b 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2791,8 +2791,8 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot if constexpr (sizeof(_UTy) > 4) { // For 64-bit numbers, work in chunks to avoid 64-bit divisions. while (_UVal > 0xFFFFFFFFU) { - auto _UVal_chunk = static_cast(_UVal % 1000000000); - _UVal /= 1000000000; + auto _UVal_chunk = static_cast(_UVal % 100000000); + _UVal /= 100000000; for (int _Idx = 0; _Idx != 4; ++_Idx) { const unsigned long _UVal_chunk_part = _UVal_chunk % 100; @@ -2800,8 +2800,6 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot _RNext -= 2; _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_chunk_part], 2 * sizeof(_Elem)); } - - *--_RNext = static_cast<_Elem>('0' + _UVal_chunk); } } From 141449f89b38fb462ef2caa414a6db7b90ca81da Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 07:16:52 -0800 Subject: [PATCH 21/23] Refine x86 optimization to avoid final division. This helps a little more: Benchmark | 4 loop | special | speedup ----------------------------------------------------------|---------|----------|-------- `internal_integer_to_buff` | 8.49 ns | 7.97 ns | 1.07 `internal_integer_to_buff` | 8.15 ns | 7.79 ns | 1.05 `integer_to_string` | 18.5 ns | 18.0 ns | 1.03 `integer_to_string` | 19.6 ns | 19.3 ns | 1.02 --- stl/inc/xmemory | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 17267e2733b..2e86091d9d0 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2794,12 +2794,15 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot auto _UVal_chunk = static_cast(_UVal % 100000000); _UVal /= 100000000; - for (int _Idx = 0; _Idx != 4; ++_Idx) { + for (int _Idx = 0; _Idx != 3; ++_Idx) { const unsigned long _UVal_chunk_part = _UVal_chunk % 100; _UVal_chunk /= 100; _RNext -= 2; _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_chunk_part], 2 * sizeof(_Elem)); } + + _RNext -= 2; + _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_chunk], 2 * sizeof(_Elem)); } } From 77ab0a789cd0a607f974ab797f9181766f48722e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 13:57:07 -0800 Subject: [PATCH 22/23] You want me to give him the CLAMPS, boss? --- benchmarks/src/integer_to_string.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index f98344a8422..4057a1c15e2 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -17,7 +18,8 @@ auto generate_array() { mt19937_64 gen; lognormal_distribution dis(M, S); - ranges::generate(a, [&] { return static_cast(dis(gen)); }); + constexpr auto max_val = static_cast(numeric_limits::max()); + ranges::generate(a, [&] { return static_cast(clamp(dis(gen), 0.0, max_val)); }); if constexpr (is_signed_v) { bernoulli_distribution b(0.5); From 74064c9eebf01e09c72834a4b807571ed1360885 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Wed, 7 Jan 2026 14:25:53 -0800 Subject: [PATCH 23/23] Clamp a while. Clamp forever! --- benchmarks/src/integer_to_string.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index 4057a1c15e2..8466545ba2d 100644 --- a/benchmarks/src/integer_to_string.cpp +++ b/benchmarks/src/integer_to_string.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -18,8 +19,16 @@ auto generate_array() { mt19937_64 gen; lognormal_distribution dis(M, S); - constexpr auto max_val = static_cast(numeric_limits::max()); - ranges::generate(a, [&] { return static_cast(clamp(dis(gen), 0.0, max_val)); }); + auto get_clamped_value = [&] { + for (;;) { + const double dbl = floor(dis(gen)); + constexpr auto max_val = static_cast(numeric_limits::max()); + if (dbl <= max_val) { + return static_cast(dbl); + } + } + }; + ranges::generate(a, get_clamped_value); if constexpr (is_signed_v) { bernoulli_distribution b(0.5);