#if defined(__linux__) || (__APPLE__ && __aarch64__) #define USING_COUNTERS #endif #include "counters/event_counter.h" #include #include #include "fast_float/fast_float.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include template double findmax_fastfloat64(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"); } answer = answer > x ? answer : x; } return answer; } template double findmax_fastfloat32(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"); } answer = answer > x ? answer : x; } return answer; } counters::event_collector collector{}; #ifdef USING_COUNTERS template std::vector time_it_ns(std::vector> &lines, T const &function, size_t repeat) { std::vector aggregate; bool printed_bug = false; for (size_t i = 0; i < repeat; i++) { collector.start(); double ts = function(lines); if (ts == 0 && !printed_bug) { printf("bug\n"); printed_bug = true; } aggregate.push_back(collector.end()); } return aggregate; } void pretty_print(double volume, size_t number_of_floats, std::string name, std::vector events) { double volumeMB = volume / (1024. * 1024.); double average_ns{0}; double min_ns{DBL_MAX}; double cycles_min{DBL_MAX}; double instructions_min{DBL_MAX}; double cycles_avg{0}; double instructions_avg{0}; double branches_min{0}; double branches_avg{0}; double branch_misses_min{0}; double branch_misses_avg{0}; for (counters::event_count e : events) { double ns = e.elapsed_ns(); average_ns += ns; min_ns = min_ns < ns ? min_ns : ns; double cycles = e.cycles(); cycles_avg += cycles; cycles_min = cycles_min < cycles ? cycles_min : cycles; double instructions = e.instructions(); instructions_avg += instructions; instructions_min = instructions_min < instructions ? instructions_min : instructions; double branches = e.branches(); branches_avg += branches; branches_min = branches_min < branches ? branches_min : branches; double branch_misses = e.branch_misses(); branch_misses_avg += branch_misses; branch_misses_min = branch_misses_min < branch_misses ? branch_misses_min : branch_misses; } cycles_avg /= events.size(); instructions_avg /= events.size(); average_ns /= events.size(); branches_avg /= events.size(); printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(), volumeMB * 1000000000 / min_ns, (average_ns - min_ns) * 100.0 / average_ns); printf("%8.2f Mfloat/s ", number_of_floats * 1000 / min_ns); if (instructions_min > 0) { printf(" %8.2f i/B %8.2f i/f (+/- %.1f %%) ", instructions_min / volume, instructions_min / number_of_floats, (instructions_avg - instructions_min) * 100.0 / instructions_avg); printf(" %8.2f c/B %8.2f c/f (+/- %.1f %%) ", cycles_min / volume, cycles_min / number_of_floats, (cycles_avg - cycles_min) * 100.0 / cycles_avg); printf(" %8.2f i/c ", instructions_min / cycles_min); printf(" %8.2f b/f ", branches_avg / number_of_floats); printf(" %8.2f bm/f ", branch_misses_avg / number_of_floats); printf(" %8.2f GHz ", cycles_min / min_ns); } printf("\n"); } #else template std::pair time_it_ns(std::vector> &lines, T const &function, size_t repeat) { std::chrono::high_resolution_clock::time_point t1, t2; double average = 0; double min_value = DBL_MAX; bool printed_bug = false; for (size_t i = 0; i < repeat; i++) { t1 = std::chrono::high_resolution_clock::now(); double ts = function(lines); if (ts == 0 && !printed_bug) { printf("bug\n"); printed_bug = true; } t2 = std::chrono::high_resolution_clock::now(); double dif = std::chrono::duration_cast(t2 - t1).count(); average += dif; min_value = min_value < dif ? min_value : dif; } average /= repeat; return std::make_pair(min_value, average); } void pretty_print(double volume, size_t number_of_floats, std::string name, std::pair result) { double volumeMB = volume / (1024. * 1024.); printf("%-40s: %8.2f MB/s (+/- %.1f %%) ", name.data(), volumeMB * 1000000000 / result.first, (result.second - result.first) * 100.0 / result.second); printf("%8.2f Mfloat/s ", number_of_floats * 1000 / result.first); printf(" %8.2f ns/f \n", double(result.first) / number_of_floats); } #endif // this is okay, all chars are ASCII inline std::u16string widen(std::string line) { std::u16string u16line; u16line.resize(line.size()); for (size_t i = 0; i < line.size(); ++i) { u16line[i] = char16_t(line[i]); } return u16line; } std::vector widen(const std::vector &lines) { std::vector u16lines; u16lines.reserve(lines.size()); for (auto const &line : lines) { u16lines.push_back(widen(line)); } return u16lines; } void process(std::vector &lines, size_t volume) { size_t repeat = 1000; double volumeMB = volume / (1024. * 1024.); 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 (32)", time_it_ns(lines, findmax_fastfloat32, repeat)); std::vector lines16 = widen(lines); volume = 2 * volume; volumeMB = volume / (1024. * 1024.); 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 (32)", time_it_ns(lines16, findmax_fastfloat32, repeat)); } void fileload(std::string filename) { std::ifstream inputfile(filename); if (!inputfile) { std::cerr << "can't open " << filename << std::endl; return; } std::cout << "#### " << std::endl; std::cout << "# reading " << filename << std::endl; std::cout << "#### " << std::endl; std::string line; std::vector lines; lines.reserve(10000); // let us reserve plenty of memory. size_t volume = 0; while (getline(inputfile, line)) { volume += line.size(); lines.push_back(line); } std::cout << "# read " << lines.size() << " lines " << std::endl; process(lines, volume); } namespace { constexpr size_t truncated_fraction_integer_digits = fast_float::binary_format::max_digits() + 1; constexpr size_t truncated_fraction_max_digits = fast_float::binary_format::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( 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 double measure_truncated_fraction( std::array const &inputs, size_t iterations, size_t batches, Function function) { std::array 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(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 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 { #if defined(__linux__) || (__APPLE__ && __aarch64__) std::cout << "# Hardware counters not available, try to run in privileged " "mode (e.g., sudo)." << std::endl; #endif } if (argc > 1) { fileload(argv[1]); return EXIT_SUCCESS; } fileload(std::string(BENCHMARK_DATA_DIR) + "/canada.txt"); fileload(std::string(BENCHMARK_DATA_DIR) + "/mesh.txt"); return EXIT_SUCCESS; }