Skip to content

Commit c7cce34

Browse files
committed
Refactor to make it easier to test fixed-length
1 parent c1affe4 commit c7cce34

File tree

5 files changed

+106
-102
lines changed

5 files changed

+106
-102
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ algorithms that output fixed-length representation of a given length:
102102
./build/benchmarks/benchmark -f data/canada.txt -F [length]
103103
```
104104

105+
Note that this only works when we are comparing speeds, not measuring properties
106+
of the algorithms, i.e., we can't use both `-F/--fixed` and `-t/--test` at the same time.
107+
105108
## Other existing benchmarks
106109

107110
- [dtoa Benchmark](https://github.com/miloyip/dtoa-benchmark)

benchmarks/algorithms.h

Lines changed: 90 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -44,44 +44,24 @@
4444
#define YY_DOUBLE_SUPPORTED 0
4545
#endif
4646

47-
namespace Benchmarks {
48-
49-
enum Algorithm {
50-
DRAGON4 = 0,
51-
ERROL3 = 1,
52-
TO_STRING = 2,
53-
FMT_FORMAT = 3,
54-
NETLIB = 4,
55-
SNPRINTF = 5,
56-
GRISU2 = 6,
57-
GRISU_EXACT = 7,
58-
SCHUBFACH = 8,
59-
DRAGONBOX = 9,
60-
RYU = 10,
61-
TEJU_JAGUA = 11,
62-
DOUBLE_CONVERSION = 12,
63-
ABSEIL = 13,
64-
STD_TO_CHARS = 14,
65-
GRISU3 = 15,
66-
SWIFT_DTOA = 16,
67-
YY_DOUBLE = 17,
68-
COUNT // Keep last
69-
};
70-
7147
template<arithmetic_float T>
7248
struct BenchArgs {
7349
using Type = T;
50+
using BenchFn = std::function<int(T, std::span<char>&, size_t fixed_size)>;
7451

75-
BenchArgs(const std::string& name = {}, int (*func)(T, std::span<char>&) = {},
76-
bool used = true, size_t testRepeat = 100)
77-
: name(name), func(func), used(used), testRepeat(testRepeat) {}
52+
BenchArgs(const std::string& name = {}, BenchFn func = {}, bool used = true,
53+
size_t testRepeat = 100, size_t fixedSize = 9)
54+
: name(name), func(func), used(used), testRepeat(testRepeat), fixedSize(fixedSize) {}
7855

7956
std::string name{};
80-
int (*func)(T, std::span<char>&){};
57+
BenchFn func{};
8158
bool used{};
8259
size_t testRepeat{100};
60+
size_t fixedSize{9};
8361
};
8462

63+
namespace BenchmarkShortest {
64+
8565
template<arithmetic_float T>
8666
int dragon4(T d, std::span<char>& buffer) {
8767
if constexpr (std::is_same_v<T, float>)
@@ -187,14 +167,6 @@ int netlib(T d, std::span<char>& buffer) {
187167
#endif
188168
}
189169

190-
template<arithmetic_float T>
191-
int snprintf(T d, std::span<char>& buffer) {
192-
if constexpr (std::is_same_v<T, float>)
193-
return std::snprintf(buffer.data(), buffer.size(), "%.9g", d);
194-
else
195-
return std::snprintf(buffer.data(), buffer.size(), "%.17g", d);
196-
}
197-
198170
// grisu2 is hardcoded for double.
199171
template<arithmetic_float T>
200172
int grisu2(T d, std::span<char>& buffer) {
@@ -284,19 +256,6 @@ int yy_double(T d, std::span<char>& buffer) {
284256
#endif
285257
}
286258

287-
template<arithmetic_float T>
288-
int abseil(T d, std::span<char>& buffer) {
289-
// StrAppend is faster but only outputs 6 digits after the decimal point
290-
// std::string s;
291-
// absl::StrAppend(&s, d);
292-
// std::copy(s.begin(), s.end(), buffer.begin());
293-
// return size(s);
294-
if constexpr (std::is_same_v<T, float>)
295-
return absl::SNPrintF(buffer.data(), buffer.size(), "%.9g", d);
296-
else
297-
return absl::SNPrintF(buffer.data(), buffer.size(), "%.17g", d);
298-
}
299-
300259
template<arithmetic_float T>
301260
int std_to_chars(T d, std::span<char>& buffer) {
302261
#if TO_CHARS_SUPPORTED
@@ -313,34 +272,91 @@ int std_to_chars(T d, std::span<char>& buffer) {
313272
#endif
314273
}
315274

275+
} // namespace BenchmarksShortest
276+
277+
namespace BenchmarkFixedSize {
278+
279+
template<arithmetic_float T>
280+
int abseil(T d, std::span<char>& buffer, size_t fixed_size) {
281+
// StrAppend is faster but only outputs 6 digits after the decimal point
282+
// std::string s;
283+
// absl::StrAppend(&s, d);
284+
// std::copy(s.begin(), s.end(), buffer.begin());
285+
// return size(s);
286+
if constexpr (std::is_same_v<T, float>)
287+
return absl::SNPrintF(buffer.data(), buffer.size(), "%.9g", d);
288+
else
289+
return absl::SNPrintF(buffer.data(), buffer.size(), "%.17g", d);
290+
}
291+
292+
template<arithmetic_float T>
293+
int snprintf(T d, std::span<char>& buffer, size_t fixed_size) {
294+
if constexpr (std::is_same_v<T, float>)
295+
return std::snprintf(buffer.data(), buffer.size(), "%.9g", d);
296+
else
297+
return std::snprintf(buffer.data(), buffer.size(), "%.17g", d);
298+
}
299+
300+
} // namespace BenchmarksShortest
301+
302+
template <typename T>
303+
auto make_shortest_adapter(int (*fn)(T, std::span<char>&)) {
304+
return [fn](T v, std::span<char>& buf, size_t /*fixed_size*/) -> int {
305+
return fn(v, buf);
306+
};
307+
}
308+
309+
template <typename T>
310+
auto make_fixed_adapter(int (*fn)(T, std::span<char>&, size_t)) {
311+
return [fn](T v, std::span<char>& buf, size_t fixed_size) -> int {
312+
return fn(v, buf, fixed_size);
313+
};
314+
}
315+
316316
template <arithmetic_float T>
317-
std::array<BenchArgs<T>, Benchmarks::COUNT> initArgs(size_t fixed_size, bool use_errol = false) {
318-
if (fixed_size == 0) { // shortest length representation
319-
std::array<BenchArgs<T>, Benchmarks::COUNT> args;
320-
args[Benchmarks::DRAGON4] = { "dragon4" , Benchmarks::dragon4<T> , true , 10 };
321-
args[Benchmarks::ERROL3] = { "errol3" , Benchmarks::errol3<T> , ERROL_SUPPORTED && use_errol };
322-
args[Benchmarks::TO_STRING] = { "std::to_string" , Benchmarks::to_string<T> , true };
323-
args[Benchmarks::FMT_FORMAT] = { "fmt::format" , Benchmarks::fmt_format<T> , true };
324-
args[Benchmarks::NETLIB] = { "netlib" , Benchmarks::netlib<T> , NETLIB_SUPPORTED && std::is_same_v<T, double>, 10 };
325-
args[Benchmarks::SNPRINTF] = { "snprintf" , Benchmarks::snprintf<T> , true };
326-
args[Benchmarks::GRISU2] = { "grisu2" , Benchmarks::grisu2<T> , std::is_same_v<T, double> };
327-
args[Benchmarks::GRISU_EXACT] = { "grisu_exact" , Benchmarks::grisu_exact<T> , true };
328-
args[Benchmarks::SCHUBFACH] = { "schubfach" , Benchmarks::schubfach<T> , true };
329-
args[Benchmarks::DRAGONBOX] = { "dragonbox" , Benchmarks::dragonbox<T> , true };
330-
args[Benchmarks::RYU] = { "ryu" , Benchmarks::ryu<T> , true };
331-
args[Benchmarks::TEJU_JAGUA] = { "teju_jagua" , Benchmarks::teju_jagua<T> , true };
332-
args[Benchmarks::DOUBLE_CONVERSION] = { "double_conversion" , Benchmarks::double_conversion<T> , true };
333-
args[Benchmarks::ABSEIL] = { "abseil" , Benchmarks::abseil<T> , ABSEIL_SUPPORTED };
334-
args[Benchmarks::STD_TO_CHARS] = { "std::to_chars" , Benchmarks::std_to_chars<T> , TO_CHARS_SUPPORTED };
335-
args[Benchmarks::GRISU3] = { "grisu3" , Benchmarks::grisu3<T> , std::is_same_v<T, double> };
336-
args[Benchmarks::SWIFT_DTOA] = { "SwiftDtoa" , Benchmarks::swiftDtoa<T> , SWIFT_LIB_SUPPORTED };
337-
args[Benchmarks::YY_DOUBLE] = { "yy_double" , Benchmarks::yy_double<T> , YY_DOUBLE_SUPPORTED && std::is_same_v<T, double> };
338-
return args;
317+
std::vector<BenchArgs<T>> initArgs(bool use_errol = false, size_t repeat = 0, size_t fixed_size = 0) {
318+
std::vector<BenchArgs<T>> args;
319+
if (fixed_size == 0) { // shortest-length representation
320+
auto&& wrap = make_shortest_adapter<T>;
321+
namespace s = BenchmarkShortest;
322+
args.emplace_back("dragon4" , wrap(s::dragon4<T>) , true , 10);
323+
args.emplace_back("netlib" , wrap(s::netlib<T>) , NETLIB_SUPPORTED && std::is_same_v<T, double> , 10);
324+
args.emplace_back("errol3" , wrap(s::errol3<T>) , ERROL_SUPPORTED && use_errol);
325+
args.emplace_back("fmt_format" , wrap(s::fmt_format<T>) , true);
326+
args.emplace_back("grisu2" , wrap(s::grisu2<T>) , std::is_same_v<T, double>);
327+
args.emplace_back("grisu3" , wrap(s::grisu3<T>) , std::is_same_v<T, double>);
328+
args.emplace_back("grisu_exact" , wrap(s::grisu_exact<T>) , true);
329+
args.emplace_back("schubfach" , wrap(s::schubfach<T>) , true);
330+
args.emplace_back("dragonbox" , wrap(s::dragonbox<T>) , true);
331+
args.emplace_back("ryu" , wrap(s::ryu<T>) , true);
332+
args.emplace_back("teju_jagua" , wrap(s::teju_jagua<T>) , true);
333+
args.emplace_back("double_conversion" , wrap(s::double_conversion<T>) , true);
334+
args.emplace_back("swiftDtoa" , wrap(s::swiftDtoa<T>) , SWIFT_LIB_SUPPORTED);
335+
args.emplace_back("yy_double" , wrap(s::yy_double<T>) , YY_DOUBLE_SUPPORTED && std::is_same_v<T, double>);
336+
args.emplace_back("std::to_chars" , wrap(s::std_to_chars<T>) , TO_CHARS_SUPPORTED);
337+
338+
// to_string, snprintf and abseil do not support shortest-length representation
339339
} else { // fixed-length representation
340-
throw std::runtime_error("fixed length representation not yet implemented");
340+
auto&& wrap = make_fixed_adapter<T>;
341+
namespace f = BenchmarkFixedSize;
342+
args.emplace_back("snprintf" , wrap(f::snprintf<T>) , true);
343+
args.emplace_back("abseil" , wrap(f::abseil<T>) , ABSEIL_SUPPORTED);
344+
345+
// to_string is hard-coded for 6 digits after the decimal point
346+
// args.emplace_back("to_string", BenchmarkFixedSize::to_string<T>, true);
347+
348+
fmt::println("# testing fixed-size output to {} digits", fixed_size);
349+
for (auto &arg : args)
350+
arg.fixedSize = fixed_size;
341351
}
342-
};
343352

344-
} // namespace Benchmarks
353+
if (repeat > 0) {
354+
fmt::println("# forcing repeat count to {}", repeat);
355+
for (auto &arg : args)
356+
arg.testRepeat = repeat;
357+
}
358+
359+
return args;
360+
};
345361

346362
#endif

benchmarks/benchmark.cpp

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,9 @@
2525
#include <fast_float/fast_float.h>
2626
#include <fmt/core.h>
2727

28-
using Benchmarks::BenchArgs;
29-
3028
template <arithmetic_float T>
3129
void evaluateProperties(const std::vector<TestCase<T>> &lines,
32-
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args,
30+
const std::vector<BenchArgs<T>> &args,
3331
const std::vector<std::string> &algo_filter) {
3432
evaluate_properties_helper<T>(lines, algo_filter, args);
3533
}
@@ -44,7 +42,7 @@ struct diy_float_t {
4442

4543
template <arithmetic_float T>
4644
void process(const std::vector<TestCase<T>> &lines,
47-
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args,
45+
const std::vector<BenchArgs<T>> &args,
4846
const std::vector<std::string> &algo_filter) {
4947
// We have a special algorithm for the string generation:
5048
if (!algo_filtered_out("just_string", algo_filter)) {
@@ -95,7 +93,7 @@ void process(const std::vector<TestCase<T>> &lines,
9593
char buf[100];
9694
std::span<char> bufspan(buf, sizeof(buf));
9795
for (const auto d : lines)
98-
volume += algo.func(d.value, bufspan);
96+
volume += algo.func(d.value, bufspan, algo.fixedSize);
9997
return volume;
10098
}, algo.testRepeat);
10199
}
@@ -170,7 +168,6 @@ int main(int argc, char **argv) {
170168
fmt::print("{}\n", options.help());
171169
return EXIT_SUCCESS;
172170
}
173-
const size_t repeat = result["repeat"].as<size_t>();
174171
const bool single = result["single"].as<bool>();
175172
const auto filter = result.count("algo-filter")
176173
? result["algo-filter"].as<std::vector<std::string>>()
@@ -197,22 +194,14 @@ int main(int argc, char **argv) {
197194
numbers = fileload<double>(filename);
198195
}
199196

200-
std::variant<std::array<BenchArgs<float>, Benchmarks::COUNT>,
201-
std::array<BenchArgs<double>, Benchmarks::COUNT>> algorithms;
197+
std::variant<std::vector<BenchArgs<float>>, std::vector<BenchArgs<double>>> algorithms;
202198
const bool errol = result["errol"].as<bool>();
199+
const size_t repeat = result["repeat"].as<size_t>();
203200
const size_t fixed_size = result["fixed"].as<size_t>();
204201
if (single)
205-
algorithms = Benchmarks::initArgs<float>(fixed_size, errol);
202+
algorithms = initArgs<float>(errol, repeat, fixed_size);
206203
else
207-
algorithms = Benchmarks::initArgs<double>(fixed_size, errol);
208-
209-
if(repeat > 0) {
210-
fmt::println("# forcing repeat count to {}", repeat);
211-
std::visit([repeat](auto &args) {
212-
for (auto &arg : args)
213-
arg.testRepeat = repeat;
214-
}, algorithms);
215-
}
204+
algorithms = initArgs<double>(errol, repeat, fixed_size);
216205

217206
const bool test = result["test"].as<bool>();
218207
std::visit([test, &filter](const auto &lines, const auto &args) {

benchmarks/benchutil.h

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
#include "algorithms.h"
1414
#include "counters/event_counter.h"
1515

16-
using Benchmarks::BenchArgs;
17-
1816
event_collector collector;
1917

2018
bool algo_filtered_out(const std::string &algo_name,
@@ -50,11 +48,11 @@ concept TestCaseRange
5048
template<arithmetic_float T, typename Range> requires TestCaseRange<Range, T>
5149
void evaluate_properties_helper(Range&& cases,
5250
const std::vector<std::string> &algo_filter,
53-
std::variant<std::array<BenchArgs<T>, Benchmarks::COUNT>, bool> argsOpt) {
51+
std::variant<std::vector<BenchArgs<T>>, bool> argsOpt) {
5452
fmt::println("{:20} {:20}", "Algorithm", "Valid shortest serialization");
5553
const auto args = std::holds_alternative<bool>(argsOpt)
56-
? Benchmarks::initArgs<T>(std::get<bool>(argsOpt))
57-
: std::get<std::array<BenchArgs<T>, Benchmarks::COUNT>>(argsOpt);
54+
? initArgs<T>(std::get<bool>(argsOpt))
55+
: std::get<std::vector<BenchArgs<T>>>(argsOpt);
5856

5957
// Get number of cases for progress display
6058
uint64_t total = 0;
@@ -69,7 +67,7 @@ void evaluate_properties_helper(Range&& cases,
6967
fmt::println("# skipping {}", algo.name);
7068
continue;
7169
}
72-
if (algo.func == Benchmarks::dragonbox<T>) {
70+
if (algo.name == "dragonbox") {
7371
fmt::println("# skipping {} because it is the reference.", algo.name);
7472
continue;
7573
}
@@ -102,8 +100,8 @@ void evaluate_properties_helper(Range&& cases,
102100
// the shortest representation, which is not necessarily the same as the
103101
// representation using the fewest significant digits.
104102
// So we use dragonbox, which serves as the reference implementation.
105-
const size_t vRef = Benchmarks::dragonbox(d, bufRef);
106-
const size_t vAlgo = algo.func(d, bufAlgo);
103+
const size_t vRef = BenchmarkShortest::dragonbox(d, bufRef);
104+
const size_t vAlgo = algo.func(d, bufAlgo, algo.fixedSize);
107105

108106
std::string_view svRef{bufRef.data(), vRef},
109107
svAlgo{bufAlgo.data(), vAlgo};

benchmarks/exhaustivefloat32.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
#include "floatutils.h"
99
#include "benchutil.h"
1010

11-
using Benchmarks::BenchArgs;
12-
1311
void run_exhaustive32(bool errol, const std::vector<std::string>& algo_filter = {}) {
1412
static_assert(sizeof(float) == sizeof(uint32_t));
1513
auto floats_view

0 commit comments

Comments
 (0)