diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 59d84e3aec0..ce912d68979 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -113,6 +113,7 @@ 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(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) diff --git a/benchmarks/src/integer_to_string.cpp b/benchmarks/src/integer_to_string.cpp new file mode 100644 index 00000000000..8466545ba2d --- /dev/null +++ b/benchmarks/src/integer_to_string.cpp @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +template +auto generate_array() { + array a; + + mt19937_64 gen; + lognormal_distribution dis(M, S); + 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); + 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(); + + CharT 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_end, 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(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(); diff --git a/stl/inc/xmemory b/stl/inc/xmemory index fcd92f799af..2e86091d9d0 100644 --- a/stl/inc/xmemory +++ b/stl/inc/xmemory @@ -2762,6 +2762,24 @@ 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 @@ -2773,23 +2791,43 @@ _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; - - for (int _Idx = 0; _Idx != 9; ++_Idx) { - *--_RNext = static_cast<_Elem>('0' + _UVal_chunk % 10); - _UVal_chunk /= 10; + auto _UVal_chunk = static_cast(_UVal % 100000000); + _UVal /= 100000000; + + 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)); } } auto _UVal_trunc = static_cast(_UVal); #endif // ^^^ !defined(_WIN64) ^^^ + // 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 { - *--_RNext = static_cast<_Elem>('0' + _UVal_trunc % 10); - _UVal_trunc /= 10; - } while (_UVal_trunc != 0); + 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); + } + return _RNext; } _STD_END 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]".