Skip to content

Commit 59a6cf1

Browse files
authored
Merge pull request #33 from fastfloat/some_tuning
Some tuning
2 parents ff56c15 + 2dd412a commit 59a6cf1

File tree

6 files changed

+310
-139
lines changed

6 files changed

+310
-139
lines changed

benchmarks/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ add_executable(benchmark
33
)
44
add_library(benchmark_deps INTERFACE)
55
add_library(ieeeToString ieeeToString.cpp)
6-
target_include_directories(ieeeToString PRIVATE ${ryu_SOURCE_DIR})
76
target_link_libraries(benchmark_deps INTERFACE ieeeToString)
87
include(CheckSourceCompiles)
98
check_source_compiles(CXX "

benchmarks/algorithms.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ struct BenchArgs {
8282
std::string name{};
8383
int (*func)(T, std::span<char>&){};
8484
bool used{};
85-
unsigned char testRepeat{100};
85+
size_t testRepeat{100};
8686
};
8787

8888
template<arithmetic_float T>

benchmarks/benchmark.cpp

Lines changed: 84 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
#include "algorithms.h"
11+
#include <vector>
1112
#define IEEE_8087
1213
#include "benchutil.h"
1314
#include "cxxopts.hpp"
@@ -22,24 +23,37 @@
2223
#include <string>
2324
#include <variant>
2425
#include <fast_float/fast_float.h>
26+
#include <fmt/core.h>
2527

2628
using Benchmarks::arithmetic_float;
2729
using Benchmarks::BenchArgs;
2830

31+
bool is_matched(const std::string &str, const std::span<std::string> filter) {
32+
if (filter.empty()) {
33+
return true;
34+
}
35+
for (const auto &f : filter) {
36+
if (str.find(f) != std::string::npos) {
37+
return true;
38+
}
39+
}
40+
return false;
41+
}
42+
2943
template <arithmetic_float T>
3044
void evaluateProperties(const std::vector<T> &lines,
31-
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::string& filter = "") {
45+
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::span<std::string> filter = {}) {
3246
constexpr auto precision = std::numeric_limits<T>::digits10;
3347
fmt::println("{:20} {:20}", "Algorithm", "Valid round-trip");
3448

3549
for (const auto &algo : args) {
3650
if (!algo.used) {
37-
std::cout << "# skipping " << algo.name << std::endl;
51+
fmt::println("# skipping {}", algo.name);
3852
continue;
3953
}
4054
// Apply filter if provided
41-
if (!filter.empty() && std::string(filter).find(algo.name) == std::string::npos) {
42-
std::cout << "# filtered out " << algo.name << std::endl;
55+
if (!is_matched(algo.name, filter)) {
56+
fmt::println("# filtered out {}", algo.name);
4357
continue;
4458
}
4559
char buf1[100], buf2[100];
@@ -71,17 +85,42 @@ void evaluateProperties(const std::vector<T> &lines,
7185
}
7286
}
7387

88+
struct diy_float_t {
89+
uint64_t significand;
90+
int exponent;
91+
bool is_negative;
92+
};
93+
7494
template <arithmetic_float T>
7595
void process(const std::vector<T> &lines,
76-
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::string& filter = "") {
96+
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::span<std::string> filter = {}) {
97+
// We have a special algorithm for the string generation:
98+
std::string just_string = "just_string";
99+
if (is_matched(just_string, filter)) {
100+
std::vector<diy_float_t> parsed;
101+
for(auto d : lines) {
102+
auto v = jkj::grisu_exact(d);
103+
parsed.emplace_back(v.significand, v.exponent, v.is_negative);
104+
}
105+
pretty_print(parsed, just_string, [](const std::vector<diy_float_t>& parsed) -> int {
106+
int volume = 0;
107+
char buf[100];
108+
std::span<char> bufspan(buf, sizeof(buf));
109+
for (const auto v : parsed)
110+
volume += to_chars(v.significand, v.exponent, v.is_negative, bufspan.data());
111+
return volume;
112+
}, 100);
113+
} else {
114+
fmt::println("# skipping {}", just_string);
115+
}
77116
for (const auto &algo : args) {
78117
if (!algo.used) {
79-
std::cout << "# skipping " << algo.name << std::endl;
118+
fmt::println("# skipping {}", algo.name);
80119
continue;
81120
}
82121
// Apply filter if provided
83-
if (!filter.empty() && std::string(filter).find(algo.name) == std::string::npos) {
84-
std::cout << "# filtered out " << algo.name << std::endl;
122+
if (!is_matched(algo.name, filter)) {
123+
fmt::println("# filtered out {}", algo.name);
85124
continue;
86125
}
87126
pretty_print(lines, algo.name, [&algo](const std::vector<T> &lines) -> int {
@@ -93,13 +132,14 @@ void process(const std::vector<T> &lines,
93132
return volume;
94133
}, algo.testRepeat);
95134
}
135+
96136
}
97137

98138
template <typename T>
99139
std::vector<T> fileload(const std::string &filename) {
100140
std::ifstream inputfile(filename);
101141
if (!inputfile) {
102-
std::cerr << "can't open " << filename << std::endl;
142+
fmt::print(stderr, "can't open {}\n", filename);
103143
return {};
104144
}
105145

@@ -110,24 +150,21 @@ std::vector<T> fileload(const std::string &filename) {
110150
lines.push_back(std::is_same_v<T, float> ? std::stof(line)
111151
: std::stod(line));
112152
} catch (...) {
113-
std::cerr << "problem with " << line << "\n"
114-
<< "We expect floating-point numbers (one per line)."
115-
<< std::endl;
153+
fmt::print(stderr, "problem with {}\nWe expect floating-point numbers (one per line).\n", line);
116154
std::abort();
117155
}
118156
}
119-
std::cout << "# read " << lines.size() << " lines " << std::endl;
157+
fmt::println("# read {} lines", lines.size());
120158
return lines;
121159
}
122160

123161
template <typename T>
124162
std::vector<T> get_random_numbers(size_t howmany,
125163
const std::string &random_model) {
126-
std::cout << "# parsing random numbers" << std::endl;
164+
fmt::println("# parsing random numbers");
127165
std::vector<T> lines;
128166
auto g = get_generator_by_name<T>(random_model);
129-
std::cout << "model: " << g->describe() << "\n"
130-
<< "volume: " << howmany << " floats" << std::endl;
167+
fmt::print("model: {}\nvolume: {} floats\n", g->describe(), howmany);
131168
lines.reserve(howmany); // let us reserve plenty of memory.
132169
for (size_t i = 0; i < howmany; i++) {
133170
const T line = g->new_float();
@@ -155,20 +192,21 @@ int main(int argc, char **argv) {
155192
cxxopts::value<bool>()->default_value("false"))
156193
("e,errol", "Enable errol3 (current impl. returns invalid values, e.g., for 0).",
157194
cxxopts::value<bool>()->default_value("false"))
158-
("a,algo-filter", "Filter algorithms by name substring.",
159-
cxxopts::value<std::string>()->default_value(""))
195+
("a,algo-filter", "Filter algorithms by name substring: you can use multiple filters separated by commas.",
196+
cxxopts::value<std::vector<std::string>>()->default_value(""))
197+
("r,repeat", "Force a number of repetitions.",
198+
cxxopts::value<size_t>()->default_value("0"))
160199
("h,help", "Print usage.");
161200
const auto result = options.parse(argc, argv);
162201

163202
if (result["help"].as<bool>()) {
164-
std::cout << options.help() << std::endl;
203+
fmt::print("{}\n", options.help());
165204
return EXIT_SUCCESS;
166205
}
167-
206+
const size_t repeat = result["repeat"].as<size_t>();
168207
const bool single = result["single"].as<bool>();
169-
const std::string filter = result["algo-filter"].as<std::string>();
170-
std::cout << "number type: binary"
171-
<< (single ? "32 (float)" : "64 (double)") << std::endl;
208+
std::vector<std::string> filter = result["algo-filter"].as<std::vector<std::string>>();
209+
fmt::println("number type: binary{}", (single ? "32 (float)" : "64 (double)"));
172210

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

237+
if(repeat > 0) {
238+
fmt::println("# forcing repeat count to {}", repeat);
239+
std::visit([repeat](auto &args) {
240+
for (auto &arg : args)
241+
arg.testRepeat = repeat;
242+
}, algorithms);
243+
}
244+
201245
const bool test = result["test"].as<bool>();
202246
std::visit([test,&filter](const auto &lines, const auto &args) {
203247
using T1 = typename std::decay_t<decltype(lines)>::value_type;
@@ -210,7 +254,20 @@ int main(int argc, char **argv) {
210254
}
211255
}, numbers, algorithms);
212256
} catch (const std::exception &e) {
213-
std::cout << "error parsing options: " << e.what() << std::endl;
257+
fmt::println("Error parsing options: {}", e.what());
258+
fmt::println("\nUSAGE GUIDE:");
259+
fmt::println(" ./benchmark [OPTIONS]");
260+
fmt::println("\nCOMMAND SUMMARY:");
261+
fmt::println(" The benchmark tool evaluates the performance of different floating-point to string");
262+
fmt::println(" conversion algorithms. It can use either synthetic data or a file containing");
263+
fmt::println(" floating-point numbers (one per line).");
264+
fmt::println("\nEXAMPLES:");
265+
fmt::println(" ./benchmark --single # Run benchmark with single precision (float)");
266+
fmt::println(" ./benchmark --file=data/canada.txt # Run benchmark using numbers from a file");
267+
fmt::println(" ./benchmark --test # Test correctness instead of performance");
268+
fmt::println(" ./benchmark --volume=1000 --model=uniform # Generate 1000 uniform random numbers");
269+
fmt::println(" ./benchmark --algo-filter=ryu,grisu # Only test algorithms containing 'ryu' or 'grisu'");
270+
fmt::println("\nFor full options list, run: ./benchmark --help");
214271
return EXIT_FAILURE;
215272
}
216273
}

benchmarks/exhaustivefloat32.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <iostream>
99
#include <string_view>
1010
#include <charconv>
11+
#include <vector>
1112

1213
#include "algorithms.h"
1314
#include "cxxopts.hpp"
@@ -47,19 +48,19 @@ std::optional<float> parse_float(std::string_view sv) {
4748
float result;
4849
const char* begin = sv.data();
4950
const char* end = sv.data() + sv.size();
50-
51+
5152
auto [ptr, ec] = std::from_chars(begin, end, result);
52-
53+
5354
// Check if parsing succeeded and consumed the entire string
5455
if (ec == std::errc{} && ptr == end) {
5556
return result;
5657
}
57-
58+
5859
// Return nullopt if parsing failed or didn't consume all input
5960
return std::nullopt;
6061
}
6162

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

@@ -75,6 +76,22 @@ void run_exhaustive32(bool errol) {
7576
fmt::print("# skipping {} because it is the reference.\n", algo.name);
7677
continue;
7778
}
79+
80+
// Apply filter if provided
81+
if (!algo_filter.empty()) {
82+
bool matched = false;
83+
for (const auto &f : algo_filter) {
84+
if (algo.name.find(f) != std::string::npos) {
85+
matched = true;
86+
break;
87+
}
88+
}
89+
if (!matched) {
90+
fmt::print("# filtered out {}\n", algo.name);
91+
continue;
92+
}
93+
}
94+
7895
bool incorrect = false;
7996
char buf1[100], buf2[100];
8097
std::span<char> bufRef(buf1, sizeof(buf1)), bufAlgo(buf2, sizeof(buf2));
@@ -149,15 +166,21 @@ int main(int argc, char **argv) {
149166
options.add_options()(
150167
"e,errol",
151168
"Enable errol3 (current impl. returns invalid values, e.g., for 0).",
152-
cxxopts::value<bool>()->default_value("false"))("h,help",
153-
"Print usage.");
169+
cxxopts::value<bool>()->default_value("false"))(
170+
"a,algorithm",
171+
"Specify which algorithm(s) to test (comma-separated).",
172+
cxxopts::value<std::vector<std::string>>()->default_value({}))(
173+
"h,help",
174+
"Print usage.");
154175
const auto result = options.parse(argc, argv);
155176

156177
if (result["help"].as<bool>()) {
157178
fmt::print("{}\n", options.help());
158179
return EXIT_SUCCESS;
159180
}
160-
run_exhaustive32(result["errol"].as<bool>());
181+
182+
auto algo_filter = result["algorithm"].as<std::vector<std::string>>();
183+
run_exhaustive32(result["errol"].as<bool>(), algo_filter);
161184
} catch (const std::exception &e) {
162185
fmt::print("error parsing options: {}\n", e.what());
163186
return EXIT_FAILURE;

0 commit comments

Comments
 (0)