From 9ed4b90638c1a61b34d3b6cfbd8de78e5235e076 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Mon, 9 Nov 2026 12:06:33 -0500 Subject: [PATCH] Benchmark fast_float with and without FASTFLOAT_ROUNDS_TO_NEAREST realbenchmark now also times the parsing loops compiled with FASTFLOAT_ROUNDS_TO_NEAREST (benchmark_rtn.cpp). That file renames the fast_float namespace so that both configurations coexist in one binary without an ODR violation. --- README.md | 4 +++ benchmarks/CMakeLists.txt | 2 +- benchmarks/benchmark.cpp | 14 +++++++++++ benchmarks/benchmark_rtn.cpp | 49 ++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 benchmarks/benchmark_rtn.cpp diff --git a/README.md b/README.md index c8f9376..d9d0b04 100644 --- a/README.md +++ b/README.md @@ -681,6 +681,10 @@ cmake --build build Importantly, by default, the benchmark is built in Release mode. +The lines marked `assume nearest` time the same code compiled with +`FASTFLOAT_ROUNDS_TO_NEAREST` (see [Assuming the default rounding +mode](#assuming-the-default-rounding-mode)). + The instructions are similar under Windows. Under Linux and macOS, it is recommended to run the benchmarks in a privileged manner to get access diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 81ea92a..4deced3 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -8,7 +8,7 @@ FetchContent_Declare( FetchContent_MakeAvailable(counters) -add_executable(realbenchmark benchmark.cpp) +add_executable(realbenchmark benchmark.cpp benchmark_rtn.cpp) target_link_libraries(realbenchmark PRIVATE counters::counters) add_executable(bench_ip bench_ip.cpp) add_executable(bench_uint16 bench_uint16.cpp) diff --git a/benchmarks/benchmark.cpp b/benchmarks/benchmark.cpp index 22b5a6b..04afb9d 100644 --- a/benchmarks/benchmark.cpp +++ b/benchmarks/benchmark.cpp @@ -50,6 +50,12 @@ double findmax_fastfloat32(std::vector> &s) { return answer; } +// Defined in benchmark_rtn.cpp, with FASTFLOAT_ROUNDS_TO_NEAREST. +template +double findmax_fastfloat64_rtn(std::vector> &s); +template +double findmax_fastfloat32_rtn(std::vector> &s); + counters::event_collector collector{}; #ifdef USING_COUNTERS @@ -193,8 +199,12 @@ void process(std::vector &lines, size_t volume) { std::cout << "ASCII volume = " << volumeMB << " MB " << std::endl; pretty_print(volume, lines.size(), "fastfloat (64)", time_it_ns(lines, findmax_fastfloat64, repeat)); + pretty_print(volume, lines.size(), "fastfloat (64, assume nearest)", + time_it_ns(lines, findmax_fastfloat64_rtn, repeat)); pretty_print(volume, lines.size(), "fastfloat (32)", time_it_ns(lines, findmax_fastfloat32, repeat)); + pretty_print(volume, lines.size(), "fastfloat (32, assume nearest)", + time_it_ns(lines, findmax_fastfloat32_rtn, repeat)); std::vector lines16 = widen(lines); volume = 2 * volume; @@ -202,8 +212,12 @@ void process(std::vector &lines, size_t volume) { std::cout << "UTF-16 volume = " << volumeMB << " MB " << std::endl; pretty_print(volume, lines.size(), "fastfloat (64)", time_it_ns(lines16, findmax_fastfloat64, repeat)); + pretty_print(volume, lines.size(), "fastfloat (64, assume nearest)", + time_it_ns(lines16, findmax_fastfloat64_rtn, repeat)); pretty_print(volume, lines.size(), "fastfloat (32)", time_it_ns(lines16, findmax_fastfloat32, repeat)); + pretty_print(volume, lines.size(), "fastfloat (32, assume nearest)", + time_it_ns(lines16, findmax_fastfloat32_rtn, repeat)); } void fileload(std::string filename) { diff --git a/benchmarks/benchmark_rtn.cpp b/benchmarks/benchmark_rtn.cpp new file mode 100644 index 0000000..1892161 --- /dev/null +++ b/benchmarks/benchmark_rtn.cpp @@ -0,0 +1,49 @@ +// The same parsing loops as findmax_fastfloat64 and findmax_fastfloat32 in +// benchmark.cpp, but compiled with FASTFLOAT_ROUNDS_TO_NEAREST: fast_float +// assumes the default rounding mode and skips its check before the fast path. +// +// We rename the fast_float namespace in this file. Otherwise, the two +// configurations would define the same inline functions differently, and the +// linker could keep either one (an ODR violation). +#include +#include +#include + +#define FASTFLOAT_ROUNDS_TO_NEAREST +#define fast_float fast_float_rtn +#include "fast_float/fast_float.h" + +template +double findmax_fastfloat64_rtn(std::vector> &s) { + double answer = 0; + double x = 0; + for (auto &st : s) { + auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x); + if (p == st.data()) { + throw std::runtime_error("bug in findmax_fastfloat_rtn"); + } + answer = answer > x ? answer : x; + } + return answer; +} + +template +double findmax_fastfloat32_rtn(std::vector> &s) { + float answer = 0; + float x = 0; + for (auto &st : s) { + auto [p, ec] = fast_float::from_chars(st.data(), st.data() + st.size(), x); + if (p == st.data()) { + throw std::runtime_error("bug in findmax_fastfloat_rtn"); + } + answer = answer > x ? answer : x; + } + return answer; +} + +template double findmax_fastfloat64_rtn(std::vector &); +template double findmax_fastfloat32_rtn(std::vector &); +template double +findmax_fastfloat64_rtn(std::vector &); +template double +findmax_fastfloat32_rtn(std::vector &);