Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ add_executable(benchmark
)
add_library(benchmark_deps INTERFACE)
add_library(ieeeToString ieeeToString.cpp)
target_include_directories(ieeeToString PRIVATE ${ryu_SOURCE_DIR})
target_link_libraries(benchmark_deps INTERFACE ieeeToString)
include(CheckSourceCompiles)
check_source_compiles(CXX "
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/algorithms.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ struct BenchArgs {
std::string name{};
int (*func)(T, std::span<char>&){};
bool used{};
unsigned char testRepeat{100};
size_t testRepeat{100};
};

template<arithmetic_float T>
Expand Down
111 changes: 84 additions & 27 deletions benchmarks/benchmark.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

#include "algorithms.h"
#include <vector>
#define IEEE_8087
#include "benchutil.h"
#include "cxxopts.hpp"
Expand All @@ -22,24 +23,37 @@
#include <string>
#include <variant>
#include <fast_float/fast_float.h>
#include <fmt/core.h>

using Benchmarks::arithmetic_float;
using Benchmarks::BenchArgs;

bool is_matched(const std::string &str, const std::span<std::string> filter) {
if (filter.empty()) {
return true;
}
for (const auto &f : filter) {
if (str.find(f) != std::string::npos) {
return true;
}
}
return false;
}

template <arithmetic_float T>
void evaluateProperties(const std::vector<T> &lines,
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::string& filter = "") {
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::span<std::string> filter = {}) {
constexpr auto precision = std::numeric_limits<T>::digits10;
fmt::println("{:20} {:20}", "Algorithm", "Valid round-trip");

for (const auto &algo : args) {
if (!algo.used) {
std::cout << "# skipping " << algo.name << std::endl;
fmt::println("# skipping {}", algo.name);
continue;
}
// Apply filter if provided
if (!filter.empty() && std::string(filter).find(algo.name) == std::string::npos) {
std::cout << "# filtered out " << algo.name << std::endl;
if (!is_matched(algo.name, filter)) {
fmt::println("# filtered out {}", algo.name);
continue;
}
char buf1[100], buf2[100];
Expand Down Expand Up @@ -71,17 +85,42 @@ void evaluateProperties(const std::vector<T> &lines,
}
}

struct diy_float_t {
uint64_t significand;
int exponent;
bool is_negative;
};

template <arithmetic_float T>
void process(const std::vector<T> &lines,
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::string& filter = "") {
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::span<std::string> filter = {}) {
// We have a special algorithm for the string generation:
std::string just_string = "just_string";
if (is_matched(just_string, filter)) {
std::vector<diy_float_t> parsed;
for(auto d : lines) {
auto v = jkj::grisu_exact(d);
parsed.emplace_back(v.significand, v.exponent, v.is_negative);
}
pretty_print(parsed, just_string, [](const std::vector<diy_float_t>& parsed) -> int {
int volume = 0;
char buf[100];
std::span<char> bufspan(buf, sizeof(buf));
for (const auto v : parsed)
volume += to_chars(v.significand, v.exponent, v.is_negative, bufspan.data());
return volume;
}, 100);
} else {
fmt::println("# skipping {}", just_string);
}
for (const auto &algo : args) {
if (!algo.used) {
std::cout << "# skipping " << algo.name << std::endl;
fmt::println("# skipping {}", algo.name);
continue;
}
// Apply filter if provided
if (!filter.empty() && std::string(filter).find(algo.name) == std::string::npos) {
std::cout << "# filtered out " << algo.name << std::endl;
if (!is_matched(algo.name, filter)) {
fmt::println("# filtered out {}", algo.name);
continue;
}
pretty_print(lines, algo.name, [&algo](const std::vector<T> &lines) -> int {
Expand All @@ -93,13 +132,14 @@ void process(const std::vector<T> &lines,
return volume;
}, algo.testRepeat);
}

}

template <typename T>
std::vector<T> fileload(const std::string &filename) {
std::ifstream inputfile(filename);
if (!inputfile) {
std::cerr << "can't open " << filename << std::endl;
fmt::print(stderr, "can't open {}\n", filename);
return {};
}

Expand All @@ -110,24 +150,21 @@ std::vector<T> fileload(const std::string &filename) {
lines.push_back(std::is_same_v<T, float> ? std::stof(line)
: std::stod(line));
} catch (...) {
std::cerr << "problem with " << line << "\n"
<< "We expect floating-point numbers (one per line)."
<< std::endl;
fmt::print(stderr, "problem with {}\nWe expect floating-point numbers (one per line).\n", line);
std::abort();
}
}
std::cout << "# read " << lines.size() << " lines " << std::endl;
fmt::println("# read {} lines", lines.size());
return lines;
}

template <typename T>
std::vector<T> get_random_numbers(size_t howmany,
const std::string &random_model) {
std::cout << "# parsing random numbers" << std::endl;
fmt::println("# parsing random numbers");
std::vector<T> lines;
auto g = get_generator_by_name<T>(random_model);
std::cout << "model: " << g->describe() << "\n"
<< "volume: " << howmany << " floats" << std::endl;
fmt::print("model: {}\nvolume: {} floats\n", g->describe(), howmany);
lines.reserve(howmany); // let us reserve plenty of memory.
for (size_t i = 0; i < howmany; i++) {
const T line = g->new_float();
Expand Down Expand Up @@ -155,20 +192,21 @@ int main(int argc, char **argv) {
cxxopts::value<bool>()->default_value("false"))
("e,errol", "Enable errol3 (current impl. returns invalid values, e.g., for 0).",
cxxopts::value<bool>()->default_value("false"))
("a,algo-filter", "Filter algorithms by name substring.",
cxxopts::value<std::string>()->default_value(""))
("a,algo-filter", "Filter algorithms by name substring: you can use multiple filters separated by commas.",
cxxopts::value<std::vector<std::string>>()->default_value(""))
("r,repeat", "Force a number of repetitions.",
cxxopts::value<size_t>()->default_value("0"))
("h,help", "Print usage.");
const auto result = options.parse(argc, argv);

if (result["help"].as<bool>()) {
std::cout << options.help() << std::endl;
fmt::print("{}\n", options.help());
return EXIT_SUCCESS;
}

const size_t repeat = result["repeat"].as<size_t>();
const bool single = result["single"].as<bool>();
const std::string filter = result["algo-filter"].as<std::string>();
std::cout << "number type: binary"
<< (single ? "32 (float)" : "64 (double)") << std::endl;
std::vector<std::string> filter = result["algo-filter"].as<std::vector<std::string>>();
fmt::println("number type: binary{}", (single ? "32 (float)" : "64 (double)"));

std::variant<std::vector<float>, std::vector<double>> numbers;
const auto filename = result["file"].as<std::string>();
Expand All @@ -179,9 +217,7 @@ int main(int argc, char **argv) {
numbers = get_random_numbers<float>(volume, model);
else
numbers = get_random_numbers<double>(volume, model);
std::cout << "# You can also provide a filename (with the -f flag): "
"it should contain one string per line corresponding to a number"
<< std::endl;
fmt::println("# You can also provide a filename (with the -f flag): it should contain one string per line corresponding to a number");
}
else {
if (single)
Expand All @@ -198,6 +234,14 @@ int main(int argc, char **argv) {
else
algorithms = Benchmarks::initArgs<double>(errol);

if(repeat > 0) {
fmt::println("# forcing repeat count to {}", repeat);
std::visit([repeat](auto &args) {
for (auto &arg : args)
arg.testRepeat = repeat;
}, algorithms);
}

const bool test = result["test"].as<bool>();
std::visit([test,&filter](const auto &lines, const auto &args) {
using T1 = typename std::decay_t<decltype(lines)>::value_type;
Expand All @@ -210,7 +254,20 @@ int main(int argc, char **argv) {
}
}, numbers, algorithms);
} catch (const std::exception &e) {
std::cout << "error parsing options: " << e.what() << std::endl;
fmt::println("Error parsing options: {}", e.what());
fmt::println("\nUSAGE GUIDE:");
fmt::println(" ./benchmark [OPTIONS]");
fmt::println("\nCOMMAND SUMMARY:");
fmt::println(" The benchmark tool evaluates the performance of different floating-point to string");
fmt::println(" conversion algorithms. It can use either synthetic data or a file containing");
fmt::println(" floating-point numbers (one per line).");
fmt::println("\nEXAMPLES:");
fmt::println(" ./benchmark --single # Run benchmark with single precision (float)");
fmt::println(" ./benchmark --file=data/canada.txt # Run benchmark using numbers from a file");
fmt::println(" ./benchmark --test # Test correctness instead of performance");
fmt::println(" ./benchmark --volume=1000 --model=uniform # Generate 1000 uniform random numbers");
fmt::println(" ./benchmark --algo-filter=ryu,grisu # Only test algorithms containing 'ryu' or 'grisu'");
fmt::println("\nFor full options list, run: ./benchmark --help");
return EXIT_FAILURE;
}
}
37 changes: 30 additions & 7 deletions benchmarks/exhaustivefloat32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <iostream>
#include <string_view>
#include <charconv>
#include <vector>

#include "algorithms.h"
#include "cxxopts.hpp"
Expand Down Expand Up @@ -47,19 +48,19 @@ std::optional<float> parse_float(std::string_view sv) {
float result;
const char* begin = sv.data();
const char* end = sv.data() + sv.size();

auto [ptr, ec] = std::from_chars(begin, end, result);

// Check if parsing succeeded and consumed the entire string
if (ec == std::errc{} && ptr == end) {
return result;
}

// Return nullopt if parsing failed or didn't consume all input
return std::nullopt;
}

void run_exhaustive32(bool errol) {
void run_exhaustive32(bool errol, const std::vector<std::string>& algo_filter = {}) {
constexpr auto precision = std::numeric_limits<float>::digits10;
fmt::println("{:20} {:20}", "Algorithm", "Valid shortest serialization");

Expand All @@ -75,6 +76,22 @@ void run_exhaustive32(bool errol) {
fmt::print("# skipping {} because it is the reference.\n", algo.name);
continue;
}

// Apply filter if provided
if (!algo_filter.empty()) {
bool matched = false;
for (const auto &f : algo_filter) {
if (algo.name.find(f) != std::string::npos) {
matched = true;
break;
}
}
if (!matched) {
fmt::print("# filtered out {}\n", algo.name);
continue;
}
}

bool incorrect = false;
char buf1[100], buf2[100];
std::span<char> bufRef(buf1, sizeof(buf1)), bufAlgo(buf2, sizeof(buf2));
Expand Down Expand Up @@ -149,15 +166,21 @@ int main(int argc, char **argv) {
options.add_options()(
"e,errol",
"Enable errol3 (current impl. returns invalid values, e.g., for 0).",
cxxopts::value<bool>()->default_value("false"))("h,help",
"Print usage.");
cxxopts::value<bool>()->default_value("false"))(
"a,algorithm",
"Specify which algorithm(s) to test (comma-separated).",
cxxopts::value<std::vector<std::string>>()->default_value({}))(
"h,help",
"Print usage.");
const auto result = options.parse(argc, argv);

if (result["help"].as<bool>()) {
fmt::print("{}\n", options.help());
return EXIT_SUCCESS;
}
run_exhaustive32(result["errol"].as<bool>());

auto algo_filter = result["algorithm"].as<std::vector<std::string>>();
run_exhaustive32(result["errol"].as<bool>(), algo_filter);
} catch (const std::exception &e) {
fmt::print("error parsing options: {}\n", e.what());
return EXIT_FAILURE;
Expand Down
Loading