Skip to content

Commit a42b24c

Browse files
authored
Add support for binary32 (float) numbers (#9)
* generalize benchutils functions to any type T * generalize "process" function to support float * generalize random numbers generators * Use concepts !
1 parent e4177da commit a42b24c

File tree

7 files changed

+285
-226
lines changed

7 files changed

+285
-226
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.15)
22

33
project(SimpleFastFloatBenchmark VERSION 0.1.0 LANGUAGES CXX C)
4-
set(CMAKE_CXX_STANDARD 17)
4+
set(CMAKE_CXX_STANDARD 20)
55
set(CMAKE_CXX_STANDARD_REQUIRED ON)
66
set(CMAKE_C_STANDARD 99)
77
set(CMAKE_C_STANDARD_REQUIRED ON)

benchmarks/CMakeLists.txt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,17 @@ include(CheckSourceCompiles)
77
check_source_compiles(CXX "
88
#include <charconv>
99
int main(void) {
10-
double value = 0;
10+
float valuef = 0;
11+
double valued = 0;
1112
const char* ptr;
12-
std::from_chars_result result = std::from_chars(ptr, ptr, value, std::chars_format::general);
13+
std::from_chars_result result1 = std::from_chars(ptr, ptr, valuef, std::chars_format::general);
14+
std::from_chars_result result2 = std::from_chars(ptr, ptr, valued, std::chars_format::general);
1315
return 0;
1416
}
15-
" from_chars_double)
17+
" from_chars_ok)
1618

17-
if (from_chars_double)
18-
target_compile_definitions(benchmark PUBLIC FROM_CHARS_DOUBLE_SUPPORTED=1)
19+
if (from_chars_ok)
20+
target_compile_definitions(benchmark PUBLIC FROM_CHARS_SUPPORTED=1)
1921
endif()
2022

2123
if (NOT WIN32)

benchmarks/benchmark.cpp

Lines changed: 111 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
#include "double-conversion/double-conversion.h"
2020
#include "grisu_exact.h"
2121
#include "dragon4.h"
22+
#include "schubfach_32.h"
2223
#include "schubfach_64.h"
24+
2325
#if __has_include("errol.h")
2426
#include "errol.h"
25-
#define ERROL_SUPPORTED 1
26-
#else
27-
#define ERROL_SUPPORTED 0
27+
#define ERROL_SUPPORTED
2828
#endif
2929

3030
#define IEEE_8087
@@ -39,64 +39,72 @@
3939
#include "random_generators.h"
4040
#include "ieeeToString.h"
4141

42-
#include <charconv>
42+
#include <fmt/format.h>
43+
4344
#include <climits>
4445
#include <cmath>
4546
#include <cstdio>
46-
#include <cstdlib>
47-
#include <cstring>
48-
#include <float.h>
49-
#include <fmt/format.h>
5047
#include <fstream>
5148
#include <iostream>
52-
#include <limits.h>
53-
#include <stdio.h>
5449
#include <string>
5550
#include <vector>
5651

57-
#if FROM_CHARS_DOUBLE_SUPPORTED
52+
#if FROM_CHARS_SUPPORTED
5853
#include <charconv>
5954
#endif
6055

61-
void process(std::vector<double> &lines) {
62-
pretty_print(lines, "dragon4", [](const std::vector<double> &lines) {
63-
double volume = 0;
56+
template <typename T>
57+
concept arithmetic_float
58+
= std::is_same_v<T, float> || std::is_same_v<T, double>;
59+
60+
template <arithmetic_float T>
61+
void process(const std::vector<T> &lines) {
62+
using MantissaType = std::conditional_t<std::is_same_v<T, float>,
63+
uint32_t, uint64_t>;
64+
using IEEE754Type = std::conditional_t<std::is_same_v<T, float>,
65+
IEEE754f, IEEE754d>;
66+
67+
// No dragon4 implementation optimized for float instead of double ?
68+
pretty_print(lines, "dragon4", [](const std::vector<T> &lines) -> int {
69+
int volume = 0;
6470
for (const auto d : lines) {
6571
uint64_t dmantissa;
6672
int dexp;
67-
const IEEE754d fields = decode_ieee754(d);
73+
const IEEE754Type fields = decode_ieee754(d);
6874
dragon4::Dragon4(dmantissa, dexp, fields.mantissa, fields.exponent,
6975
true, true);
7076
char buffer[100];
7177
volume += to_chars(dmantissa, dexp, fields.sign, buffer);
7278
}
7379
return volume;
74-
});
75-
76-
#if ERROL_SUPPORTED
77-
pretty_print(lines, "errol3", [](const std::vector<double> &lines) {
78-
double volume = 0;
80+
}, 10);
81+
82+
#ifdef ERROL_SUPPORTED
83+
// No errol3 implementation optimized for float instead of double ?
84+
pretty_print(lines, "errol3", [](const std::vector<T> &lines) -> int {
85+
int volume = 0;
7986
char buffer[100];
8087
for (const auto d : lines) {
81-
errol3_dtoa(d, buffer); // returns the exponent?
88+
errol3_dtoa(d, buffer); // returns the exponent
8289
volume += std::strlen(buffer);
8390
}
8491
return volume;
8592
});
8693
#else
8794
std::cout << "# errol not supported" << std::endl;
88-
#endif // ERROL_SUPPORTED
89-
pretty_print(lines, "std::to_string", [](const std::vector<double> &lines) {
90-
double volume = 0;
95+
#endif
96+
97+
pretty_print(lines, "std::to_string", [](const std::vector<T> &lines) -> int {
98+
int volume = 0;
9199
for (const auto d : lines) {
92100
const std::string s = std::to_string(d);
93101
volume += s.size();
94102
}
95103
return volume;
96104
});
97105

98-
pretty_print(lines, "fmt::format", [](const std::vector<double> &lines) {
99-
double volume = 0;
106+
pretty_print(lines, "fmt::format", [](const std::vector<T> &lines) -> int {
107+
int volume = 0;
100108
for (const auto d : lines) {
101109
const std::string s = fmt::format("{}", d);
102110
volume += s.size();
@@ -105,8 +113,9 @@ void process(std::vector<double> &lines) {
105113
});
106114

107115
#if NETLIB_SUPPORTED
108-
pretty_print(lines, "netlib", [](const std::vector<double> &lines) {
109-
double volume = 0;
116+
// There's no "ftoa", only "dtoa", so not optimized for float.
117+
pretty_print(lines, "netlib", [](const std::vector<T> &lines) -> int {
118+
int volume = 0;
110119
char *result;
111120
int decpt, sign;
112121
char *rve;
@@ -126,17 +135,19 @@ void process(std::vector<double> &lines) {
126135
std::cout << "# netlib not supported" << std::endl;
127136
#endif
128137

129-
pretty_print(lines, "sprintf", [](const std::vector<double> &lines) {
130-
double volume = 0;
138+
pretty_print(lines, "sprintf", [](const std::vector<T> &lines) -> int {
139+
int volume = 0;
131140
char buffer[100];
132141
for (const auto d : lines) {
133142
volume += snprintf(buffer, sizeof(buffer), "%g", d);
134143
}
135144
return volume;
136145
});
137146

138-
pretty_print(lines, "grisu2", [](const std::vector<double> &lines) {
139-
double volume = 0;
147+
// grisu2::dtoa_impl::grisu2 can take a template type
148+
// However, grisu2::to_chars is hardcoded for double.
149+
pretty_print(lines, "grisu2", [](const std::vector<T> &lines) -> int {
150+
int volume = 0;
140151
char buffer[100];
141152
for (const auto d : lines) {
142153
const char *newp = grisu2::to_chars(buffer, nullptr, d);
@@ -145,8 +156,8 @@ void process(std::vector<double> &lines) {
145156
return volume;
146157
});
147158

148-
pretty_print(lines, "grisu_exact", [](const std::vector<double> &lines) {
149-
double volume = 0;
159+
pretty_print(lines, "grisu_exact", [](const std::vector<T> &lines) -> int {
160+
int volume = 0;
150161
char buffer[100];
151162
for (const auto d : lines) {
152163
auto v = jkj::grisu_exact(d);
@@ -155,18 +166,20 @@ void process(std::vector<double> &lines) {
155166
return volume;
156167
});
157168

158-
pretty_print(lines, "schubfach", [](const std::vector<double> &lines) {
159-
double volume = 0;
169+
pretty_print(lines, "schubfach", [](const std::vector<T> &lines) -> int {
170+
int volume = 0;
160171
char buffer[100];
161172
for (const auto d : lines) {
162-
const char *end_ptr = schubfach::Dtoa(buffer, d);
173+
const char* end_ptr = std::is_same_v<T, float>
174+
? schubfach::Ftoa(buffer, d)
175+
: schubfach::Dtoa(buffer, d);
163176
volume += end_ptr - &buffer[0];
164177
}
165178
return volume;
166179
});
167180

168-
pretty_print(lines, "dragonbox", [](const std::vector<double> &lines) {
169-
double volume = 0;
181+
pretty_print(lines, "dragonbox", [](const std::vector<T> &lines) -> int {
182+
int volume = 0;
170183
char buffer[100];
171184
for (const auto d : lines) {
172185
const char *end_ptr = jkj::dragonbox::to_chars(d, buffer);
@@ -175,38 +188,42 @@ void process(std::vector<double> &lines) {
175188
return volume;
176189
});
177190

178-
pretty_print(lines, "ryu", [](const std::vector<double> &lines) {
179-
double volume = 0;
191+
pretty_print(lines, "ryu", [](const std::vector<T> &lines) -> int {
192+
int volume = 0;
180193
char buffer[100];
181194
for (const auto d : lines) {
182-
volume += d2s_buffered_n(d, buffer);
195+
volume += std::is_same_v<T, float> ? f2s_buffered_n(d, buffer)
196+
: d2s_buffered_n(d, buffer);
183197
}
184198
return volume;
185199
});
186200

187-
pretty_print(lines, "teju_jagua", [](const std::vector<double> &lines) {
188-
double volume = 0;
201+
pretty_print(lines, "teju_jagua", [](const std::vector<T> &lines) -> int {
202+
int volume = 0;
189203
char buffer[100];
190204
for (const auto d : lines) {
191-
const auto fields = teju::traits_t<double>::teju(d);
192-
const bool sign = (*reinterpret_cast<const uint64_t*>(&d) >> 63) & 1;
205+
const auto fields = teju::traits_t<T>::teju(d);
206+
const bool sign = std::signbit(d);
193207
volume += to_chars(fields.mantissa, fields.exponent, sign, buffer);
194208
}
195209
return volume;
196210
});
197211

198-
pretty_print(lines, "double_conversion", [](const std::vector<double> &lines) {
199-
double volume = 0;
212+
pretty_print(lines, "double_conversion", [](const std::vector<T> &lines) -> int {
213+
int volume = 0;
214+
constexpr int kBufferSize = 100;
215+
char buffer[kBufferSize];
200216
const double_conversion::DoubleToStringConverter converter(
201217
double_conversion::DoubleToStringConverter::NO_FLAGS, "inf", "nan", 'e',
202218
-4, 6, 0, 0);
203-
const int kBufferSize = 100;
204-
char buffer[kBufferSize];
205219
double_conversion::StringBuilder builder(buffer, kBufferSize);
206220

207221
for (const auto d : lines) {
208222
builder.Reset();
209-
if (!converter.ToShortest(d, &builder)) {
223+
const bool valid = std::is_same_v<T, float>
224+
? converter.ToShortestSingle(d, &builder)
225+
: converter.ToShortest(d, &builder);
226+
if (!valid) {
210227
std::cerr << "problem with " << d << std::endl;
211228
std::abort();
212229
}
@@ -215,8 +232,8 @@ void process(std::vector<double> &lines) {
215232
return volume;
216233
});
217234

218-
pretty_print(lines, "abseil", [](const std::vector<double> &lines) {
219-
double volume = 0;
235+
pretty_print(lines, "abseil", [](const std::vector<T> &lines) -> int {
236+
int volume = 0;
220237
std::string buffer;
221238
for (const auto d : lines) {
222239
buffer.clear();
@@ -226,10 +243,9 @@ void process(std::vector<double> &lines) {
226243
return volume;
227244
});
228245

229-
230-
#if FROM_CHARS_DOUBLE_SUPPORTED
231-
pretty_print(lines, "std::to_chars", [](const std::vector<double> &lines) {
232-
double volume = 0;
246+
#if FROM_CHARS_SUPPORTED
247+
pretty_print(lines, "std::to_chars", [](const std::vector<T> &lines) -> int {
248+
int volume = 0;
233249
char buffer[100];
234250
for (const auto d : lines) {
235251
const auto [p, ec] = std::to_chars(buffer, buffer + sizeof(buffer), d);
@@ -244,24 +260,25 @@ void process(std::vector<double> &lines) {
244260
#else
245261
std::cout << "# std::to_chars not supported" << std::endl;
246262
#endif
247-
248263
}
249264

250-
void fileload(const char *filename) {
265+
template <typename T>
266+
void fileload(const std::string &filename) {
251267
std::ifstream inputfile(filename);
252268
if (!inputfile) {
253269
std::cerr << "can't open " << filename << std::endl;
254270
return;
255271
}
256272

257-
std::vector<double> lines;
273+
std::vector<T> lines;
258274
lines.reserve(10000); // let us reserve plenty of memory.
259275
for (std::string line; getline(inputfile, line);) {
260276
try {
261-
lines.push_back(std::stod(line));
277+
lines.push_back(std::is_same_v<T, float> ? std::stof(line)
278+
: std::stod(line));
262279
} catch (...) {
263-
std::cerr << "problem with " << line << std::endl;
264-
std::cerr << "We expect floating-point numbers (one per line)."
280+
std::cerr << "problem with " << line << "\n"
281+
<< "We expect floating-point numbers (one per line)."
265282
<< std::endl;
266283
std::abort();
267284
}
@@ -270,15 +287,16 @@ void fileload(const char *filename) {
270287
process(lines);
271288
}
272289

273-
void parse_random_numbers(size_t howmany, std::string random_model) {
290+
template <typename T>
291+
void parse_random_numbers(size_t howmany, const std::string &random_model) {
274292
std::cout << "# parsing random numbers" << std::endl;
275-
std::vector<double> lines;
276-
auto g = get_generator_by_name(random_model);
277-
std::cout << "model: " << g->describe() << std::endl;
278-
std::cout << "volume: " << howmany << " floats" << std::endl;
293+
std::vector<T> lines;
294+
auto g = get_generator_by_name<T>(random_model);
295+
std::cout << "model: " << g->describe() << "\n"
296+
<< "volume: " << howmany << " floats" << std::endl;
279297
lines.reserve(howmany); // let us reserve plenty of memory.
280298
for (size_t i = 0; i < howmany; i++) {
281-
double line = g->new_float();
299+
const T line = g->new_float();
282300
lines.push_back(line);
283301
}
284302
process(lines);
@@ -296,22 +314,37 @@ int main(int argc, char **argv) {
296314
cxxopts::value<size_t>()->default_value("100000"))(
297315
"m,model", "Random Model.",
298316
cxxopts::value<std::string>()->default_value("uniform"))(
317+
"s,single", "Use single precision instead of double.",
318+
cxxopts::value<bool>()->default_value("false"))(
299319
"h,help", "Print usage.");
300-
auto result = options.parse(argc, argv);
320+
const auto result = options.parse(argc, argv);
321+
301322
if (result["help"].as<bool>()) {
302323
std::cout << options.help() << std::endl;
303324
return EXIT_SUCCESS;
304325
}
305-
auto filename = result["file"].as<std::string>();
326+
327+
const bool single = result["single"].as<bool>();
328+
std::cout << "number type: " << (single ? "binary32 (float)" : "binary64 (double)") << std::endl;
329+
330+
const auto filename = result["file"].as<std::string>();
306331
if (filename.empty()) {
307-
parse_random_numbers(result["volume"].as<size_t>(),
308-
result["model"].as<std::string>());
309-
std::cout << "# You can also provide a filename (with the -f flag): it "
310-
"should contain one "
311-
"string per line corresponding to a number"
332+
if(single) {
333+
parse_random_numbers<float>(result["volume"].as<size_t>(),
334+
result["model"].as<std::string>());
335+
} else {
336+
parse_random_numbers<double>(result["volume"].as<size_t>(),
337+
result["model"].as<std::string>());
338+
}
339+
std::cout << "# You can also provide a filename (with the -f flag):"
340+
"it should contain one string per line corresponding to a number"
312341
<< std::endl;
313-
} else {
314-
fileload(filename.c_str());
342+
}
343+
else {
344+
if(single)
345+
fileload<float>(filename);
346+
else
347+
fileload<double>(filename);
315348
}
316349
} catch (const std::exception &e) {
317350
std::cout << "error parsing options: " << e.what() << std::endl;

0 commit comments

Comments
 (0)