Skip to content

Commit 2dd412a

Browse files
author
Daniel Lemire
committed
more tuning
1 parent a684d30 commit 2dd412a

File tree

4 files changed

+73
-63
lines changed

4 files changed

+73
-63
lines changed

benchmarks/benchmark.cpp

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <string>
2424
#include <variant>
2525
#include <fast_float/fast_float.h>
26+
#include <fmt/core.h>
2627

2728
using Benchmarks::arithmetic_float;
2829
using Benchmarks::BenchArgs;
@@ -47,12 +48,12 @@ void evaluateProperties(const std::vector<T> &lines,
4748

4849
for (const auto &algo : args) {
4950
if (!algo.used) {
50-
std::cout << "# skipping " << algo.name << std::endl;
51+
fmt::println("# skipping {}", algo.name);
5152
continue;
5253
}
5354
// Apply filter if provided
5455
if (!is_matched(algo.name, filter)) {
55-
std::cout << "# filtered out " << algo.name << std::endl;
56+
fmt::println("# filtered out {}", algo.name);
5657
continue;
5758
}
5859
char buf1[100], buf2[100];
@@ -93,7 +94,7 @@ struct diy_float_t {
9394
template <arithmetic_float T>
9495
void process(const std::vector<T> &lines,
9596
const std::array<BenchArgs<T>, Benchmarks::COUNT> &args, const std::span<std::string> filter = {}) {
96-
// We have a special algorithm for the reference:
97+
// We have a special algorithm for the string generation:
9798
std::string just_string = "just_string";
9899
if (is_matched(just_string, filter)) {
99100
std::vector<diy_float_t> parsed;
@@ -110,17 +111,16 @@ void process(const std::vector<T> &lines,
110111
return volume;
111112
}, 100);
112113
} else {
113-
std::cout << "# skipping " << just_string << std::endl;
114-
114+
fmt::println("# skipping {}", just_string);
115115
}
116116
for (const auto &algo : args) {
117117
if (!algo.used) {
118-
std::cout << "# skipping " << algo.name << std::endl;
118+
fmt::println("# skipping {}", algo.name);
119119
continue;
120120
}
121121
// Apply filter if provided
122122
if (!is_matched(algo.name, filter)) {
123-
std::cout << "# filtered out " << algo.name << std::endl;
123+
fmt::println("# filtered out {}", algo.name);
124124
continue;
125125
}
126126
pretty_print(lines, algo.name, [&algo](const std::vector<T> &lines) -> int {
@@ -139,7 +139,7 @@ template <typename T>
139139
std::vector<T> fileload(const std::string &filename) {
140140
std::ifstream inputfile(filename);
141141
if (!inputfile) {
142-
std::cerr << "can't open " << filename << std::endl;
142+
fmt::print(stderr, "can't open {}\n", filename);
143143
return {};
144144
}
145145

@@ -150,24 +150,21 @@ std::vector<T> fileload(const std::string &filename) {
150150
lines.push_back(std::is_same_v<T, float> ? std::stof(line)
151151
: std::stod(line));
152152
} catch (...) {
153-
std::cerr << "problem with " << line << "\n"
154-
<< "We expect floating-point numbers (one per line)."
155-
<< std::endl;
153+
fmt::print(stderr, "problem with {}\nWe expect floating-point numbers (one per line).\n", line);
156154
std::abort();
157155
}
158156
}
159-
std::cout << "# read " << lines.size() << " lines " << std::endl;
157+
fmt::println("# read {} lines", lines.size());
160158
return lines;
161159
}
162160

163161
template <typename T>
164162
std::vector<T> get_random_numbers(size_t howmany,
165163
const std::string &random_model) {
166-
std::cout << "# parsing random numbers" << std::endl;
164+
fmt::println("# parsing random numbers");
167165
std::vector<T> lines;
168166
auto g = get_generator_by_name<T>(random_model);
169-
std::cout << "model: " << g->describe() << "\n"
170-
<< "volume: " << howmany << " floats" << std::endl;
167+
fmt::print("model: {}\nvolume: {} floats\n", g->describe(), howmany);
171168
lines.reserve(howmany); // let us reserve plenty of memory.
172169
for (size_t i = 0; i < howmany; i++) {
173170
const T line = g->new_float();
@@ -203,14 +200,13 @@ int main(int argc, char **argv) {
203200
const auto result = options.parse(argc, argv);
204201

205202
if (result["help"].as<bool>()) {
206-
std::cout << options.help() << std::endl;
203+
fmt::print("{}\n", options.help());
207204
return EXIT_SUCCESS;
208205
}
209206
const size_t repeat = result["repeat"].as<size_t>();
210207
const bool single = result["single"].as<bool>();
211208
std::vector<std::string> filter = result["algo-filter"].as<std::vector<std::string>>();
212-
std::cout << "number type: binary"
213-
<< (single ? "32 (float)" : "64 (double)") << std::endl;
209+
fmt::println("number type: binary{}", (single ? "32 (float)" : "64 (double)"));
214210

215211
std::variant<std::vector<float>, std::vector<double>> numbers;
216212
const auto filename = result["file"].as<std::string>();
@@ -221,9 +217,7 @@ int main(int argc, char **argv) {
221217
numbers = get_random_numbers<float>(volume, model);
222218
else
223219
numbers = get_random_numbers<double>(volume, model);
224-
std::cout << "# You can also provide a filename (with the -f flag): "
225-
"it should contain one string per line corresponding to a number"
226-
<< 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");
227221
}
228222
else {
229223
if (single)
@@ -241,7 +235,7 @@ int main(int argc, char **argv) {
241235
algorithms = Benchmarks::initArgs<double>(errol);
242236

243237
if(repeat > 0) {
244-
std::cout << "# forcing repeat count to " << repeat << std::endl;
238+
fmt::println("# forcing repeat count to {}", repeat);
245239
std::visit([repeat](auto &args) {
246240
for (auto &arg : args)
247241
arg.testRepeat = repeat;
@@ -260,7 +254,20 @@ int main(int argc, char **argv) {
260254
}
261255
}, numbers, algorithms);
262256
} catch (const std::exception &e) {
263-
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");
264271
return EXIT_FAILURE;
265272
}
266273
}

benchmarks/exhaustivefloat32.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ std::optional<float> parse_float(std::string_view sv) {
4848
float result;
4949
const char* begin = sv.data();
5050
const char* end = sv.data() + sv.size();
51-
51+
5252
auto [ptr, ec] = std::from_chars(begin, end, result);
53-
53+
5454
// Check if parsing succeeded and consumed the entire string
5555
if (ec == std::errc{} && ptr == end) {
5656
return result;
5757
}
58-
58+
5959
// Return nullopt if parsing failed or didn't consume all input
6060
return std::nullopt;
6161
}
@@ -76,7 +76,7 @@ void run_exhaustive32(bool errol, const std::vector<std::string>& algo_filter =
7676
fmt::print("# skipping {} because it is the reference.\n", algo.name);
7777
continue;
7878
}
79-
79+
8080
// Apply filter if provided
8181
if (!algo_filter.empty()) {
8282
bool matched = false;
@@ -91,7 +91,7 @@ void run_exhaustive32(bool errol, const std::vector<std::string>& algo_filter =
9191
continue;
9292
}
9393
}
94-
94+
9595
bool incorrect = false;
9696
char buf1[100], buf2[100];
9797
std::span<char> bufRef(buf1, sizeof(buf1)), bufAlgo(buf2, sizeof(buf2));

benchmarks/ieeeToString.cpp

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ IEEE754d decode_ieee754(double f) {
5757
}
5858

5959
////////////////////////
60-
// We should use https://en.cppreference.com/w/cpp/numeric/countl_zero
60+
// We should use https://en.cppreference.com/w/cpp/numeric/countl_zero
6161
////////////////////////
6262
#if WE_HAVE_VISUAL_STUDIO
6363
inline int leading_zeroes_64(uint64_t input_num) {
@@ -84,7 +84,7 @@ inline int leading_zeroes_64(uint64_t input_num) {
8484
inline int int_log2_64(uint64_t x) { return 63 - leading_zeroes_64(x | 1); }
8585

8686
/**
87-
* Reference:
87+
* Reference:
8888
* Daniel Lemire, "Computing the number of digits of an integer even faster," in Daniel Lemire's blog, June 3, 2021, https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/.
8989
*/
9090
inline int fast_digit_count32(uint32_t x) {
@@ -101,7 +101,7 @@ inline int fast_digit_count32(uint32_t x) {
101101

102102

103103
/**
104-
* Reference:
104+
* Reference:
105105
* Daniel Lemire, "Counting the digits of 64-bit integers," in Daniel Lemire's blog, January 7, 2025, https://lemire.me/blog/2025/01/07/counting-the-digits-of-64-bit-integers/.
106106
*/
107107
inline int fast_digit_count64(uint64_t x) {
@@ -133,12 +133,13 @@ inline int fast_digit_count64(uint64_t x) {
133133
template <typename T>
134134
int to_chars(T mantissa, int32_t exponent, bool sign, char* const result) {
135135
constexpr bool is_double = sizeof(T) == 8;
136+
static_assert(is_double || sizeof(T) == 4, "Unsupported type size");
136137

137138
int index = 0;
138139
if (sign)
139140
result[index++] = '-';
140141
// We use fast arithmetic to compute the number of digits.
141-
const uint32_t olength = is_double ? fast_digit_count64(mantissa)
142+
const uint32_t olength = is_double ? fast_digit_count64(mantissa)
142143
: fast_digit_count32(mantissa);
143144
// Print the decimal digits.
144145
// for (uint32_t i = 0; i < olength - 1; ++i) {
@@ -210,30 +211,32 @@ int to_chars(T mantissa, int32_t exponent, bool sign, char* const result) {
210211
}
211212

212213
// Print the exponent.
213-
result[index++] = 'E';
214214
int32_t exp = exponent + (int32_t) olength - 1;
215-
if (exp < 0) {
216-
result[index++] = '-';
217-
exp = -exp;
218-
}
219-
220-
const auto handle_common_cases = [&]() {
221-
if (exp >= 10) {
222-
memcpy(result + index, hundreds_digit_table + 2 * exp, 2);
223-
index += 2;
224-
} else
225-
result[index++] = (char)('0' + exp);
226-
};
227-
if constexpr (is_double) {
228-
if (exp >= 100) {
229-
const int32_t c = exp % 10;
230-
memcpy(result + index, hundreds_digit_table + 2 * (exp / 10), 2);
231-
result[index + 2] = (char) ('0' + c);
232-
index += 3;
215+
if(mantissa && exp) { // We do not print the exponent if mantissa is zero.
216+
result[index++] = 'E';
217+
if (exp < 0) {
218+
result[index++] = '-';
219+
exp = -exp;
220+
}
221+
222+
const auto handle_common_cases = [&]() {
223+
if (exp >= 10) {
224+
memcpy(result + index, hundreds_digit_table + 2 * exp, 2);
225+
index += 2;
226+
} else
227+
result[index++] = (char)('0' + exp);
228+
};
229+
if constexpr (is_double) {
230+
if (exp >= 100) {
231+
const int32_t c = exp % 10;
232+
memcpy(result + index, hundreds_digit_table + 2 * (exp / 10), 2);
233+
result[index + 2] = (char) ('0' + c);
234+
index += 3;
235+
} else
236+
handle_common_cases();
233237
} else
234238
handle_common_cases();
235-
} else
236-
handle_common_cases();
239+
}
237240

238241
return index;
239242
}

benchmarks/thoroughfloat64.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ std::optional<double> parse_double(std::string_view sv) {
5050
double result;
5151
const char* begin = sv.data();
5252
const char* end = sv.data() + sv.size();
53-
53+
5454
auto [ptr, ec] = std::from_chars(begin, end, result);
55-
55+
5656
// Check if parsing succeeded and consumed the entire string
5757
if (ec == std::errc{} && ptr == end) {
5858
return result;
5959
}
60-
60+
6161
// Return nullopt if parsing failed or didn't consume all input
6262
return std::nullopt;
6363
}
@@ -72,7 +72,7 @@ std::vector<test_case> load_doubles_from_file(const std::string& filename) {
7272
std::vector<test_case> numbers;
7373
std::ifstream file(filename);
7474
std::string line;
75-
75+
7676
if (!file.is_open()) {
7777
fmt::print("Error: Could not open file {}\n", filename);
7878
return numbers;
@@ -85,7 +85,7 @@ std::vector<test_case> load_doubles_from_file(const std::string& filename) {
8585
fmt::print("Warning: Could not parse '{}' as double, skipping\n", line);
8686
}
8787
}
88-
88+
8989
file.close();
9090
return numbers;
9191
}
@@ -113,7 +113,7 @@ void run_file_test(const std::string& filename, bool errol, const std::vector<st
113113
fmt::print("# skipping {} because it is the reference.\n", algo.name);
114114
continue;
115115
}
116-
116+
117117
// Apply filter if provided
118118
if (!algo_filter.empty()) {
119119
bool matched = false;
@@ -128,13 +128,13 @@ void run_file_test(const std::string& filename, bool errol, const std::vector<st
128128
continue;
129129
}
130130
}
131-
131+
132132
bool incorrect = false;
133133
char buf1[100], buf2[100];
134134
std::span<char> bufRef(buf1, sizeof(buf1)), bufAlgo(buf2, sizeof(buf2));
135135
fmt::print("# processing {}", algo.name);
136136
fflush(stdout);
137-
137+
138138
size_t total = test_values.size();
139139
for (size_t i = 0; i < total; ++i) {
140140
if (i % (total/10) == 0 && total > 10) {
@@ -145,7 +145,7 @@ void run_file_test(const std::string& filename, bool errol, const std::vector<st
145145
const std::string& str_value = test_values[i].str_value;
146146
if (std::isnan(d) || std::isinf(d))
147147
continue;
148-
148+
149149
const size_t vRef = Benchmarks::dragonbox(d, bufRef);
150150
const size_t vAlgo = algo.func(d, bufAlgo);
151151

@@ -157,7 +157,7 @@ void run_file_test(const std::string& filename, bool errol, const std::vector<st
157157
auto countAlgo = count_significant_digits(svAlgo);
158158
auto backRef = parse_double(svRef);
159159
auto backAlgo = parse_double(svAlgo);
160-
160+
161161
if(!backRef || !backAlgo) {
162162
incorrect = true;
163163
fmt::print(" parse error: case: {}; d = {}, bufRef = {}, bufAlgo = {}", str_value, double_to_hex(d),

0 commit comments

Comments
 (0)