From 022c476340674874ff81804957d821dbd9541389 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 11 Jan 2026 10:47:19 +0200 Subject: [PATCH 1/7] benchmark integer to_chars --- benchmarks/src/integer_to_string.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp index 8466545ba2d..74ff03ff99c 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 @@ -38,6 +39,26 @@ auto generate_array() { return a; } +template +void integer_to_chars(benchmark::State& state) { + auto a = generate_array(); + char d[20]; + + auto it = a.begin(); + for (auto _ : state) { + auto i = *it; + benchmark::DoNotOptimize(i); + auto s = to_chars(begin(d), end(d), i); + benchmark::DoNotOptimize(s.ec); + benchmark::DoNotOptimize(s.ptr); + + ++it; + if (it == a.end()) { + it = a.begin(); + } + } +} + template void internal_integer_to_buff(benchmark::State& state) { auto a = generate_array(); @@ -77,6 +98,11 @@ void integer_to_string(benchmark::State& state) { } } +BENCHMARK(integer_to_chars); +BENCHMARK(integer_to_chars); +BENCHMARK(integer_to_chars); +BENCHMARK(integer_to_chars); + BENCHMARK(internal_integer_to_buff); BENCHMARK(internal_integer_to_buff); BENCHMARK(internal_integer_to_buff); From 89c1d4befd1315d8c5832070efeadbeca9ecf873 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 11 Jan 2026 11:44:58 +0200 Subject: [PATCH 2/7] Apply #5691 to another place --- stl/inc/charconv | 53 ++++++++++++++++++++++++++++++++---------------- stl/inc/xmemory | 18 ---------------- stl/inc/xutility | 17 ++++++++++++++++ 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/stl/inc/charconv b/stl/inc/charconv index 84b87511b61..6a2b14f2f13 100644 --- a/stl/inc/charconv +++ b/stl/inc/charconv @@ -65,30 +65,49 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( case 10: { // Derived from _UIntegral_to_buff() // Performance note: Ryu's digit table should be faster here. - constexpr bool _Use_chunks = sizeof(_Unsigned) > sizeof(size_t); - - if constexpr (_Use_chunks) { // For 64-bit numbers on 32-bit platforms, work in chunks to avoid 64-bit - // divisions. - while (_Value > 0xFFFF'FFFFU) { - // Performance note: Ryu's division workaround would be faster here. - unsigned long _Chunk = static_cast(_Value % 1'000'000'000); - _Value = static_cast<_Unsigned>(_Value / 1'000'000'000); - - for (int _Idx = 0; _Idx != 9; ++_Idx) { - *--_RNext = static_cast('0' + _Chunk % 10); - _Chunk /= 10; +#ifndef _WIN64 + auto _Trunc = _Value; +#else // ^^^ defined(_WIN64) / !defined(_WIN64) vvv + + if constexpr (sizeof(_Unsigned) > 4) { // For 64-bit numbers, work in chunks to avoid 64-bit divisions. + while (_Value > 0xFFFFFFFFU) { + auto _Value_chunk = static_cast(_Value % 100000000); + _Value /= 100000000; + + for (int _Idx = 0; _Idx != 3; ++_Idx) { + const unsigned long _Value_chunk_part = _Value_chunk % 100; + _Value_chunk /= 100; + _RNext -= 2; + _CSTD memcpy(_RNext, _Digit_pairs._Data[_Value_chunk_part], 2); } + + _RNext -= 2; + _CSTD memcpy(_RNext, _Digit_pairs._Data[_Value_chunk], 2); } } - using _Truncated = conditional_t<_Use_chunks, unsigned long, _Unsigned>; + auto _Trunc = static_cast(_Value); +#endif // ^^^ !defined(_WIN64) ^^^ - _Truncated _Trunc = static_cast<_Truncated>(_Value); + // If we have a single digit, print [0, 9] and return. (This is necessary to correctly handle 0.) + if (_Trunc < 10) { + *--_RNext = static_cast('0' + _Trunc); + break; + } + // Print one or more pairs of digits. do { - *--_RNext = static_cast('0' + _Trunc % 10); - _Trunc /= 10; - } while (_Trunc != 0); + const unsigned long _Trunc_part = _Trunc % 100; + _Trunc /= 100; + _RNext -= 2; + _CSTD memcpy(_RNext, _Digit_pairs._Data[_Trunc_part], 2); + } while (_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 (_Trunc != 0) { + *--_RNext = static_cast('0' + _Trunc); + } break; } diff --git a/stl/inc/xmemory b/stl/inc/xmemory index 2e86091d9d0..c1aab2252c8 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2762,24 +2762,6 @@ namespace ranges { } // namespace ranges #endif // _HAS_CXX23 -template -struct _Digit_pair_table { - _Elem _Data[100][2]; - - 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); - } - } - - _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 diff --git a/stl/inc/xutility b/stl/inc/xutility index 06c1dfa8dcc..ea28e623321 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -7925,6 +7925,23 @@ _NODISCARD constexpr bool _Mul_overflow(const _Int _Left, const _Int _Right, _In } #endif // _HAS_CXX17 +template +struct _Digit_pair_table { + _Elem _Data[100][2]; + + 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); + } + } + + _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{}; _STD_END // TRANSITION, non-_Ugly attribute tokens From 4e1076b5b278b9c4dd98c1b5c04f1a4699a5c7ab Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 11 Jan 2026 12:02:19 +0200 Subject: [PATCH 3/7] format --- stl/inc/charconv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/charconv b/stl/inc/charconv index 6a2b14f2f13..2056286e25c 100644 --- a/stl/inc/charconv +++ b/stl/inc/charconv @@ -64,7 +64,7 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( switch (_Base) { case 10: { // Derived from _UIntegral_to_buff() - // Performance note: Ryu's digit table should be faster here. + // Performance note: Ryu's digit table should be faster here. #ifndef _WIN64 auto _Trunc = _Value; #else // ^^^ defined(_WIN64) / !defined(_WIN64) vvv @@ -103,7 +103,7 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( _CSTD memcpy(_RNext, _Digit_pairs._Data[_Trunc_part], 2); } while (_Trunc >= 10); - // If we have an unpaired digit, print it. + // 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 (_Trunc != 0) { *--_RNext = static_cast('0' + _Trunc); From f5b66b61c5fb42e0b39ce95ff8b2733b40bf1bc4 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 11 Jan 2026 13:09:59 +0200 Subject: [PATCH 4/7] constexpr --- stl/inc/charconv | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/stl/inc/charconv b/stl/inc/charconv index 2056286e25c..8211c1ec030 100644 --- a/stl/inc/charconv +++ b/stl/inc/charconv @@ -78,11 +78,21 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( const unsigned long _Value_chunk_part = _Value_chunk % 100; _Value_chunk /= 100; _RNext -= 2; - _CSTD memcpy(_RNext, _Digit_pairs._Data[_Value_chunk_part], 2); + if (!_STD _Is_constant_evaluated()) { + _CSTD memcpy(_RNext, _Digit_pairs._Data[_Value_chunk_part], 2); + } else { + _RNext[0] = _Digit_pairs._Data[_Value_chunk_part][0]; + _RNext[1] = _Digit_pairs._Data[_Value_chunk_part][1]; + } } _RNext -= 2; - _CSTD memcpy(_RNext, _Digit_pairs._Data[_Value_chunk], 2); + if (!_STD _Is_constant_evaluated()) { + _CSTD memcpy(_RNext, _Digit_pairs._Data[_Value_chunk], 2); + } else { + _RNext[0] = _Digit_pairs._Data[_Value_chunk][0]; + _RNext[1] = _Digit_pairs._Data[_Value_chunk][1]; + } } } @@ -100,7 +110,12 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( const unsigned long _Trunc_part = _Trunc % 100; _Trunc /= 100; _RNext -= 2; - _CSTD memcpy(_RNext, _Digit_pairs._Data[_Trunc_part], 2); + if (!_STD _Is_constant_evaluated()) { + _CSTD memcpy(_RNext, _Digit_pairs._Data[_Trunc_part], 2); + } else { + _RNext[0] = _Digit_pairs._Data[_Trunc_part][0]; + _RNext[1] = _Digit_pairs._Data[_Trunc_part][1]; + } } while (_Trunc >= 10); // If we have an unpaired digit, print it. From 44c685ecdcec65aafbfa54502b5d0d385945a75f Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 11 Jan 2026 14:04:36 +0200 Subject: [PATCH 5/7] Integral demotion --- stl/inc/charconv | 2 +- stl/inc/xmemory | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/charconv b/stl/inc/charconv index 8211c1ec030..f09d4b2671a 100644 --- a/stl/inc/charconv +++ b/stl/inc/charconv @@ -107,7 +107,7 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( // Print one or more pairs of digits. do { - const unsigned long _Trunc_part = _Trunc % 100; + const auto _Trunc_part = static_cast(_Trunc % 100); _Trunc /= 100; _RNext -= 2; if (!_STD _Is_constant_evaluated()) { diff --git a/stl/inc/xmemory b/stl/inc/xmemory index c1aab2252c8..7d2f38930fa 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2799,7 +2799,7 @@ _NODISCARD _Elem* _UIntegral_to_buff(_Elem* _RNext, _UTy _UVal) { // used by bot // Print one or more pairs of digits. do { - const unsigned long _UVal_trunc_part = _UVal_trunc % 100; + const auto _UVal_trunc_part = static_cast(_UVal_trunc % 100); _UVal_trunc /= 100; _RNext -= 2; _CSTD memcpy(_RNext, _Digit_pairs<_Elem>._Data[_UVal_trunc_part], 2 * sizeof(_Elem)); From 48f4be548229b878a1c904320d4825729871c7e9 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Mon, 12 Jan 2026 12:34:59 +0200 Subject: [PATCH 6/7] Remove note, as the table is used --- stl/inc/charconv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/charconv b/stl/inc/charconv index f09d4b2671a..767439ede89 100644 --- a/stl/inc/charconv +++ b/stl/inc/charconv @@ -64,7 +64,6 @@ _NODISCARD _CONSTEXPR23 to_chars_result _Integer_to_chars( switch (_Base) { case 10: { // Derived from _UIntegral_to_buff() - // Performance note: Ryu's digit table should be faster here. #ifndef _WIN64 auto _Trunc = _Value; #else // ^^^ defined(_WIN64) / !defined(_WIN64) vvv @@ -2646,3 +2645,4 @@ _STL_RESTORE_CLANG_WARNINGS #endif // ^^^ _HAS_CXX17 ^^^ #endif // _STL_COMPILER_PREPROCESSOR #endif // _CHARCONV_ + From cc5edd18e793a5adf089bd757c15c4a283ddf575 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Mon, 12 Jan 2026 12:42:00 +0200 Subject: [PATCH 7/7] trim extra whitespace --- stl/inc/charconv | 1 - 1 file changed, 1 deletion(-) diff --git a/stl/inc/charconv b/stl/inc/charconv index 767439ede89..e936921c32f 100644 --- a/stl/inc/charconv +++ b/stl/inc/charconv @@ -2645,4 +2645,3 @@ _STL_RESTORE_CLANG_WARNINGS #endif // ^^^ _HAS_CXX17 ^^^ #endif // _STL_COMPILER_PREPROCESSOR #endif // _CHARCONV_ -