From 5f342c37c3fc3a605cde2cb7afdedb7c1e9cf97a Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 28 Aug 2025 09:28:37 +0300 Subject: [PATCH 01/10] Port benchmark from #1243 comment Co-authored-by: StephanTLavavej --- benchmarks/CMakeLists.txt | 1 + benchmarks/src/charconv_floats.cpp | 194 +++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 benchmarks/src/charconv_floats.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 5f7afec336c..e9283fd520e 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -104,6 +104,7 @@ add_benchmark(adjacent_difference src/adjacent_difference.cpp) add_benchmark(adjacent_find src/adjacent_find.cpp) add_benchmark(bitset_from_string src/bitset_from_string.cpp) add_benchmark(bitset_to_string src/bitset_to_string.cpp) +add_benchmark(charconv_floats src/charconv_floats.cpp) add_benchmark(efficient_nonlocking_print src/efficient_nonlocking_print.cpp) add_benchmark(filesystem src/filesystem.cpp) add_benchmark(fill src/fill.cpp) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp new file mode 100644 index 00000000000..f8f6b4b7474 --- /dev/null +++ b/benchmarks/src/charconv_floats.cpp @@ -0,0 +1,194 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace std::chrono; + +void verify(const bool b) { + if (!b) { + puts("FAIL"); + exit(EXIT_FAILURE); + } +} + +enum class RoundTrip { Sci, Fix, Gen, Hex, Lossy }; + +constexpr chars_format chars_format_from_RoundTrip(const RoundTrip rt) { + switch (rt) { + case RoundTrip::Sci: + return chars_format::scientific; + case RoundTrip::Fix: + return chars_format::fixed; + case RoundTrip::Gen: + return chars_format::general; + case RoundTrip::Hex: + return chars_format::hex; + case RoundTrip::Lossy: + default: + puts("FAIL"); + exit(EXIT_FAILURE); + } +} + +template +void test_to_chars(benchmark::State& state, const Args&... args) { + constexpr size_t n = 2'000'000; // how many floating-point values to test + + constexpr size_t BufSize = 2'000; // more than enough + + mt19937_64 mt64; + + vector vec; + + vec.reserve(n); + for (size_t i = 0; i != n; ++i) { + using Integral = conditional_t; + const Integral val = static_cast(mt64()); + constexpr Integral inf_nan = sizeof(Floating) == 4 ? 0x7F800000U : 0x7FF0000000000000ULL; + if ((val & inf_nan) == inf_nan) { + continue; // skip INF/NAN + } + Floating flt; + static_assert(sizeof(flt) == sizeof(val)); + memcpy(&flt, &val, sizeof(flt)); + vec.push_back(flt); + } + + char buf[BufSize]; + + auto it = vec.begin(); + for (auto _ : state) { + auto result = to_chars(buf, buf + BufSize, *it, args...); + + benchmark::DoNotOptimize(result.ptr); + benchmark::DoNotOptimize(buf); + + ++it; + if (it == vec.end()) { + it = vec.begin(); + } + } + + for (const auto& elem : vec) { + const auto result = to_chars(buf, buf + BufSize, elem, args...); + verify(result.ec == errc{}); + + if constexpr (Rt == RoundTrip::Lossy) { + // skip lossy conversions + } else { + Floating round_trip; + const auto from_result = from_chars(buf, result.ptr, round_trip, chars_format_from_RoundTrip(Rt)); + verify(from_result.ec == errc{}); + verify(from_result.ptr == result.ptr); + verify(round_trip == elem); + } + } +} + +constexpr auto STL_float_plain_shortest = test_to_chars; +constexpr auto STL_double_plain_shortest = test_to_chars; + +void STL_float_scientific_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::scientific); +} + +void STL_double_scientific_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::scientific); +} + +void STL_float_fixed_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::fixed); +} + +void STL_double_fixed_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::fixed); +} + +void STL_float_general_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::general); +} + +void STL_double_general_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::general); +} + +void STL_float_hex_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::hex); +} + +void STL_double_hex_shortest(benchmark::State& state) { + test_to_chars(state, chars_format::hex); +} + +void STL_float_scientific_8(benchmark::State& state) { + test_to_chars(state, chars_format::scientific, 8); +} + +void STL_double_scientific_16(benchmark::State& state) { + test_to_chars(state, chars_format::scientific, 16); +} + +void STL_float_fixed_6_lossy(benchmark::State& state) { + test_to_chars(state, chars_format::fixed, 6); +} + +void STL_double_fixed_6_lossy(benchmark::State& state) { + test_to_chars(state, chars_format::fixed, 6); +} + +void STL_float_general_9(benchmark::State& state) { + test_to_chars(state, chars_format::general, 9); +} + +void STL_double_general_17(benchmark::State& state) { + test_to_chars(state, chars_format::general, 17); +} + +void STL_float_hex_6(benchmark::State& state) { + test_to_chars(state, chars_format::hex, 6); +} + +void STL_double_hex_13(benchmark::State& state) { + test_to_chars(state, chars_format::hex, 13); +} + +BENCHMARK(STL_float_plain_shortest); +BENCHMARK(STL_double_plain_shortest); + +BENCHMARK(STL_float_scientific_shortest); +BENCHMARK(STL_double_scientific_shortest); + +BENCHMARK(STL_float_fixed_shortest); +BENCHMARK(STL_double_fixed_shortest); + +BENCHMARK(STL_float_general_shortest); +BENCHMARK(STL_double_general_shortest); + +BENCHMARK(STL_float_hex_shortest); +BENCHMARK(STL_double_hex_shortest); + +BENCHMARK(STL_float_scientific_8); +BENCHMARK(STL_double_scientific_16); + +BENCHMARK(STL_float_fixed_6_lossy); +BENCHMARK(STL_double_fixed_6_lossy); + +BENCHMARK(STL_float_general_9); +BENCHMARK(STL_double_general_17); + +BENCHMARK(STL_float_hex_6); +BENCHMARK(STL_double_hex_13); + +BENCHMARK_MAIN(); From 3e3cbba3d83c56db33ac99e679f4fce56f7794a7 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 28 Aug 2025 10:05:57 +0300 Subject: [PATCH 02/10] use size, as we skip sometimes --- benchmarks/src/charconv_floats.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index f8f6b4b7474..36fd3140bc5 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -53,7 +53,7 @@ void test_to_chars(benchmark::State& state, const Args&... args) { vector vec; vec.reserve(n); - for (size_t i = 0; i != n; ++i) { + while (vec.size() < n) { using Integral = conditional_t; const Integral val = static_cast(mt64()); constexpr Integral inf_nan = sizeof(Floating) == 4 ? 0x7F800000U : 0x7FF0000000000000ULL; From c150dcbfff3a170009aa9e585f6856f2fe745bf4 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Thu, 28 Aug 2025 14:47:02 +0300 Subject: [PATCH 03/10] no chrono --- benchmarks/src/charconv_floats.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index 36fd3140bc5..731c8d5748a 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -3,7 +3,6 @@ #include #include -#include #include #include #include @@ -14,7 +13,6 @@ #include using namespace std; -using namespace std::chrono; void verify(const bool b) { if (!b) { From 35bded644f233c075a41e2ba6e9cb82b9d0f83b5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 10:35:20 -0700 Subject: [PATCH 04/10] Adjust headers. --- benchmarks/src/charconv_floats.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index 731c8d5748a..03113a9c5ee 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -2,12 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include #include +#include +#include +#include +#include #include -#include -#include -#include -#include #include #include #include From ffcc04e00f92e04ec21b053f2998413ffe29bc04 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 10:47:57 -0700 Subject: [PATCH 05/10] Use bit_cast. --- benchmarks/src/charconv_floats.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index 03113a9c5ee..e22cfafd118 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -59,10 +59,7 @@ void test_to_chars(benchmark::State& state, const Args&... args) { if ((val & inf_nan) == inf_nan) { continue; // skip INF/NAN } - Floating flt; - static_assert(sizeof(flt) == sizeof(val)); - memcpy(&flt, &val, sizeof(flt)); - vec.push_back(flt); + vec.push_back(bit_cast(val)); } char buf[BufSize]; From f3ba0d3ae008f7638a400037bc85537a7000f077 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 11:49:17 -0700 Subject: [PATCH 06/10] Use `auto... Args` and `BENCHMARK(X)->Name("Y")`. --- benchmarks/src/charconv_floats.cpp | 119 ++++++----------------------- 1 file changed, 22 insertions(+), 97 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index e22cfafd118..cdabe2855fa 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -41,8 +41,8 @@ constexpr chars_format chars_format_from_RoundTrip(const RoundTrip rt) { } } -template -void test_to_chars(benchmark::State& state, const Args&... args) { +template +void test_to_chars(benchmark::State& state) { constexpr size_t n = 2'000'000; // how many floating-point values to test constexpr size_t BufSize = 2'000; // more than enough @@ -66,7 +66,7 @@ void test_to_chars(benchmark::State& state, const Args&... args) { auto it = vec.begin(); for (auto _ : state) { - auto result = to_chars(buf, buf + BufSize, *it, args...); + auto result = to_chars(buf, buf + BufSize, *it, Args...); benchmark::DoNotOptimize(result.ptr); benchmark::DoNotOptimize(buf); @@ -78,7 +78,7 @@ void test_to_chars(benchmark::State& state, const Args&... args) { } for (const auto& elem : vec) { - const auto result = to_chars(buf, buf + BufSize, elem, args...); + const auto result = to_chars(buf, buf + BufSize, elem, Args...); verify(result.ec == errc{}); if constexpr (Rt == RoundTrip::Lossy) { @@ -93,98 +93,23 @@ void test_to_chars(benchmark::State& state, const Args&... args) { } } -constexpr auto STL_float_plain_shortest = test_to_chars; -constexpr auto STL_double_plain_shortest = test_to_chars; - -void STL_float_scientific_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::scientific); -} - -void STL_double_scientific_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::scientific); -} - -void STL_float_fixed_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::fixed); -} - -void STL_double_fixed_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::fixed); -} - -void STL_float_general_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::general); -} - -void STL_double_general_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::general); -} - -void STL_float_hex_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::hex); -} - -void STL_double_hex_shortest(benchmark::State& state) { - test_to_chars(state, chars_format::hex); -} - -void STL_float_scientific_8(benchmark::State& state) { - test_to_chars(state, chars_format::scientific, 8); -} - -void STL_double_scientific_16(benchmark::State& state) { - test_to_chars(state, chars_format::scientific, 16); -} - -void STL_float_fixed_6_lossy(benchmark::State& state) { - test_to_chars(state, chars_format::fixed, 6); -} - -void STL_double_fixed_6_lossy(benchmark::State& state) { - test_to_chars(state, chars_format::fixed, 6); -} - -void STL_float_general_9(benchmark::State& state) { - test_to_chars(state, chars_format::general, 9); -} - -void STL_double_general_17(benchmark::State& state) { - test_to_chars(state, chars_format::general, 17); -} - -void STL_float_hex_6(benchmark::State& state) { - test_to_chars(state, chars_format::hex, 6); -} - -void STL_double_hex_13(benchmark::State& state) { - test_to_chars(state, chars_format::hex, 13); -} - -BENCHMARK(STL_float_plain_shortest); -BENCHMARK(STL_double_plain_shortest); - -BENCHMARK(STL_float_scientific_shortest); -BENCHMARK(STL_double_scientific_shortest); - -BENCHMARK(STL_float_fixed_shortest); -BENCHMARK(STL_double_fixed_shortest); - -BENCHMARK(STL_float_general_shortest); -BENCHMARK(STL_double_general_shortest); - -BENCHMARK(STL_float_hex_shortest); -BENCHMARK(STL_double_hex_shortest); - -BENCHMARK(STL_float_scientific_8); -BENCHMARK(STL_double_scientific_16); - -BENCHMARK(STL_float_fixed_6_lossy); -BENCHMARK(STL_double_fixed_6_lossy); - -BENCHMARK(STL_float_general_9); -BENCHMARK(STL_double_general_17); - -BENCHMARK(STL_float_hex_6); -BENCHMARK(STL_double_hex_13); +BENCHMARK(test_to_chars)->Name("STL_float_plain_shortest"); +BENCHMARK(test_to_chars)->Name("STL_double_plain_shortest"); +BENCHMARK(test_to_chars)->Name("STL_float_scientific_shortest"); +BENCHMARK(test_to_chars)->Name("STL_double_scientific_shortest"); +BENCHMARK(test_to_chars)->Name("STL_float_fixed_shortest"); +BENCHMARK(test_to_chars)->Name("STL_double_fixed_shortest"); +BENCHMARK(test_to_chars)->Name("STL_float_general_shortest"); +BENCHMARK(test_to_chars)->Name("STL_double_general_shortest"); +BENCHMARK(test_to_chars)->Name("STL_float_hex_shortest"); +BENCHMARK(test_to_chars)->Name("STL_double_hex_shortest"); +BENCHMARK(test_to_chars)->Name("STL_float_scientific_8"); +BENCHMARK(test_to_chars)->Name("STL_double_scientific_16"); +BENCHMARK(test_to_chars)->Name("STL_float_fixed_6_lossy"); +BENCHMARK(test_to_chars)->Name("STL_double_fixed_6_lossy"); +BENCHMARK(test_to_chars)->Name("STL_float_general_9"); +BENCHMARK(test_to_chars)->Name("STL_double_general_17"); +BENCHMARK(test_to_chars)->Name("STL_float_hex_6"); +BENCHMARK(test_to_chars)->Name("STL_double_hex_13"); BENCHMARK_MAIN(); From dc5a34e6d65b36e8ef0ced0f9e0355c4eb3ed6e5 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 11:52:47 -0700 Subject: [PATCH 07/10] Use `consteval`, so we don't need to print FAIL. --- benchmarks/src/charconv_floats.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index cdabe2855fa..821fe29020c 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -24,7 +24,7 @@ void verify(const bool b) { enum class RoundTrip { Sci, Fix, Gen, Hex, Lossy }; -constexpr chars_format chars_format_from_RoundTrip(const RoundTrip rt) { +consteval chars_format chars_format_from_RoundTrip(const RoundTrip rt) { switch (rt) { case RoundTrip::Sci: return chars_format::scientific; @@ -36,7 +36,6 @@ constexpr chars_format chars_format_from_RoundTrip(const RoundTrip rt) { return chars_format::hex; case RoundTrip::Lossy: default: - puts("FAIL"); exit(EXIT_FAILURE); } } From 9a9a97e448197b04b14bbcb56be0901ab12c68f1 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 11:58:44 -0700 Subject: [PATCH 08/10] Inline `BufSize` to avoid pointer arithmetic. --- benchmarks/src/charconv_floats.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index 821fe29020c..b5c99021535 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -44,8 +44,6 @@ template void test_to_chars(benchmark::State& state) { constexpr size_t n = 2'000'000; // how many floating-point values to test - constexpr size_t BufSize = 2'000; // more than enough - mt19937_64 mt64; vector vec; @@ -61,11 +59,11 @@ void test_to_chars(benchmark::State& state) { vec.push_back(bit_cast(val)); } - char buf[BufSize]; + char buf[2'000]; // more than enough auto it = vec.begin(); for (auto _ : state) { - auto result = to_chars(buf, buf + BufSize, *it, Args...); + auto result = to_chars(buf, end(buf), *it, Args...); benchmark::DoNotOptimize(result.ptr); benchmark::DoNotOptimize(buf); @@ -77,7 +75,7 @@ void test_to_chars(benchmark::State& state) { } for (const auto& elem : vec) { - const auto result = to_chars(buf, buf + BufSize, elem, Args...); + const auto result = to_chars(buf, end(buf), elem, Args...); verify(result.ec == errc{}); if constexpr (Rt == RoundTrip::Lossy) { From 51bd445e4de20741c2430d3e8d96e12393efac02 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 12:04:18 -0700 Subject: [PATCH 09/10] Reduce scope of `mt19937_64 mt64`. --- benchmarks/src/charconv_floats.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index b5c99021535..eb0a472a2d4 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -43,20 +43,20 @@ consteval chars_format chars_format_from_RoundTrip(const RoundTrip rt) { template void test_to_chars(benchmark::State& state) { constexpr size_t n = 2'000'000; // how many floating-point values to test - - mt19937_64 mt64; - vector vec; - vec.reserve(n); - while (vec.size() < n) { - using Integral = conditional_t; - const Integral val = static_cast(mt64()); - constexpr Integral inf_nan = sizeof(Floating) == 4 ? 0x7F800000U : 0x7FF0000000000000ULL; - if ((val & inf_nan) == inf_nan) { - continue; // skip INF/NAN + + { + mt19937_64 mt64; + while (vec.size() < n) { + using Integral = conditional_t; + const Integral val = static_cast(mt64()); + constexpr Integral inf_nan = sizeof(Floating) == 4 ? 0x7F800000U : 0x7FF0000000000000ULL; + if ((val & inf_nan) == inf_nan) { + continue; // skip INF/NAN + } + vec.push_back(bit_cast(val)); } - vec.push_back(bit_cast(val)); } char buf[2'000]; // more than enough From 37345529bbf9ab6bc9a40c9ae804744edafbfeaa Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 16 Oct 2025 12:05:48 -0700 Subject: [PATCH 10/10] Reduce scope of `auto it`. --- benchmarks/src/charconv_floats.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/benchmarks/src/charconv_floats.cpp b/benchmarks/src/charconv_floats.cpp index eb0a472a2d4..7da1eb87fd0 100644 --- a/benchmarks/src/charconv_floats.cpp +++ b/benchmarks/src/charconv_floats.cpp @@ -61,16 +61,18 @@ void test_to_chars(benchmark::State& state) { char buf[2'000]; // more than enough - auto it = vec.begin(); - for (auto _ : state) { - auto result = to_chars(buf, end(buf), *it, Args...); + { + auto it = vec.begin(); + for (auto _ : state) { + auto result = to_chars(buf, end(buf), *it, Args...); - benchmark::DoNotOptimize(result.ptr); - benchmark::DoNotOptimize(buf); + benchmark::DoNotOptimize(result.ptr); + benchmark::DoNotOptimize(buf); - ++it; - if (it == vec.end()) { - it = vec.begin(); + ++it; + if (it == vec.end()) { + it = vec.begin(); + } } }