Add truncated-fraction benchmark mode

This commit is contained in:
Perfloop Agent 2026-08-06 13:11:36 +00:00
parent f6df0f2917
commit 27b89da50c

View File

@ -3,6 +3,7 @@
#endif
#include "counters/event_counter.h"
#include <algorithm>
#include <array>
#include "fast_float/fast_float.h"
#include <chrono>
#include <climits>
@ -226,7 +227,202 @@ void fileload(std::string filename) {
process(lines, volume);
}
namespace {
constexpr size_t truncated_fraction_integer_digits =
fast_float::binary_format<double>::max_digits() + 1;
constexpr size_t truncated_fraction_max_digits =
fast_float::binary_format<double>::max_digits();
constexpr size_t truncated_fraction_digits = 4 * 1024 * 1024;
constexpr size_t truncated_fraction_batches = 9;
constexpr size_t truncated_fraction_zero_batches = 17;
constexpr size_t truncated_fraction_direct_iterations = 2048;
constexpr size_t truncated_fraction_direct_zero_iterations = 8192;
constexpr size_t truncated_fraction_from_chars_iterations = 256;
constexpr size_t truncated_fraction_from_chars_zero_iterations = 512;
constexpr double truncated_fraction_expected_value = 0x0.607b00a417628p-1022;
#if defined(_MSC_VER)
#define FASTFLOAT_BENCH_NOINLINE __declspec(noinline)
#elif defined(__GNUC__) || defined(__clang__)
#define FASTFLOAT_BENCH_NOINLINE __attribute__((noinline))
#else
#define FASTFLOAT_BENCH_NOINLINE
#endif
struct truncated_fraction_input {
std::string text{};
fast_float::parsed_number_string parsed{};
};
[[noreturn]] void truncated_fraction_fail(char const *message) {
std::fputs(message, stderr);
std::fputc('\n', stderr);
std::exit(EXIT_FAILURE);
}
void truncated_fraction_usage() {
std::fputs("usage: realbenchmark --truncated-fraction "
"{parse_mantissa|from_chars} {nonzero|zero}\n",
stderr);
}
std::string make_truncated_fraction_input(char final_integer_digit) {
std::string result = "8385788696668661046";
result.append(truncated_fraction_integer_digits - result.size() - 1, '0');
result.push_back(final_integer_digit);
result.push_back('.');
result.append(truncated_fraction_digits, '0');
result += "e-1078";
return result;
}
void initialize_truncated_fraction_input(truncated_fraction_input &input,
char final_integer_digit) {
input.text = make_truncated_fraction_input(final_integer_digit);
fast_float::parse_options options;
input.parsed = fast_float::parse_number_string<false>(
input.text.data(), input.text.data() + input.text.size(), options, true);
if (!input.parsed.valid || !input.parsed.too_many_digits ||
input.parsed.integer.len() != truncated_fraction_integer_digits ||
input.parsed.fraction.len() != truncated_fraction_digits) {
truncated_fraction_fail("unexpected parsed input");
}
double parsed_value = 0;
auto const parsed = fast_float::from_chars(
input.text.data(), input.text.data() + input.text.size(), parsed_value);
if (parsed.ec != std::errc() ||
parsed.ptr != input.text.data() + input.text.size() ||
parsed_value != truncated_fraction_expected_value) {
truncated_fraction_fail("unexpected conversion result");
}
}
FASTFLOAT_BENCH_NOINLINE uint64_t
parse_mantissa_once(truncated_fraction_input const &input) {
fast_float::parsed_number_string number = input.parsed;
fast_float::bigint result;
size_t digits = 0;
fast_float::parse_mantissa(result, number, truncated_fraction_max_digits,
digits);
bool truncated = false;
return result.hi64(truncated) ^ (uint64_t(digits) << 1) ^ uint64_t(truncated);
}
FASTFLOAT_BENCH_NOINLINE uint64_t
from_chars_once(truncated_fraction_input const &input) {
double result = 0;
auto const parsed = fast_float::from_chars(
input.text.data(), input.text.data() + input.text.size(), result);
if (parsed.ec != std::errc() ||
parsed.ptr != input.text.data() + input.text.size()) {
truncated_fraction_fail("unexpected conversion result");
}
uint64_t bits = 0;
static_assert(sizeof(bits) == sizeof(result), "unexpected double size");
std::memcpy(&bits, &result, sizeof(bits));
return bits;
}
inline void do_not_optimize(uint64_t value) {
#if defined(__GNUC__) || defined(__clang__)
asm volatile("" : : "r"(value) : "memory");
#else
volatile uint64_t sink = value;
(void)sink;
#endif
}
template <typename Function>
double measure_truncated_fraction(
std::array<truncated_fraction_input, 2> const &inputs, size_t iterations,
size_t batches, Function function) {
std::array<double, truncated_fraction_zero_batches> samples{};
for (size_t batch = 0; batch < batches; ++batch) {
uint64_t sink = 0;
auto const start = std::chrono::steady_clock::now();
for (size_t index = 0; index < iterations; ++index) {
sink += function(inputs[index & 1]);
}
auto const finish = std::chrono::steady_clock::now();
do_not_optimize(sink);
samples[batch] =
double(
std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start)
.count()) /
double(iterations);
}
std::sort(samples.begin(), samples.begin() + batches);
return samples[batches / 2];
}
void print_truncated_fraction_measurement(double nanoseconds_per_operation) {
std::printf("{\"metric\":\"ns/op\",\"value\":%.17g}\n",
nanoseconds_per_operation);
}
int run_truncated_fraction_benchmark(char const *operation,
char const *integer_suffix) {
bool direct = false;
if (std::strcmp(operation, "parse_mantissa") == 0) {
direct = true;
} else if (std::strcmp(operation, "from_chars") != 0) {
truncated_fraction_usage();
return EXIT_FAILURE;
}
bool zero_suffix = false;
if (std::strcmp(integer_suffix, "zero") == 0) {
zero_suffix = true;
} else if (std::strcmp(integer_suffix, "nonzero") != 0) {
truncated_fraction_usage();
return EXIT_FAILURE;
}
std::array<truncated_fraction_input, 2> inputs{};
initialize_truncated_fraction_input(inputs[0], zero_suffix ? '0' : '1');
initialize_truncated_fraction_input(inputs[1], zero_suffix ? '0' : '2');
// The zero-suffix route retains the long fractional scan. Use larger batches
// and more independent samples there so that its unchanged path has a precise
// check.
size_t const iterations =
zero_suffix ? (direct ? truncated_fraction_direct_zero_iterations
: truncated_fraction_from_chars_zero_iterations)
: (direct ? truncated_fraction_direct_iterations
: truncated_fraction_from_chars_iterations);
size_t const batches = zero_suffix ? truncated_fraction_zero_batches
: truncated_fraction_batches;
if (direct) {
print_truncated_fraction_measurement(measure_truncated_fraction(
inputs, iterations, batches, [](truncated_fraction_input const &input) {
return parse_mantissa_once(input);
}));
} else {
print_truncated_fraction_measurement(measure_truncated_fraction(
inputs, iterations, batches, [](truncated_fraction_input const &input) {
return from_chars_once(input);
}));
}
return EXIT_SUCCESS;
}
#undef FASTFLOAT_BENCH_NOINLINE
} // namespace
int main(int argc, char **argv) {
if (argc > 1 && std::strcmp(argv[1], "--truncated-fraction") == 0) {
if (argc != 4) {
truncated_fraction_usage();
return EXIT_FAILURE;
}
return run_truncated_fraction_benchmark(argv[2], argv[3]);
}
if (collector.has_events()) {
std::cout << "# Using hardware counters" << std::endl;
} else {