Skip to content

Commit 0e4bec7

Browse files
committed
init
0 parents  commit 0e4bec7

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+202028
-0
lines changed

.github/workflows/ubuntu.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Ubuntu 22.04 Sanitized CI (GCC 11)
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
branches:
9+
- master
10+
11+
jobs:
12+
ubuntu-build:
13+
runs-on: ubuntu-22.04
14+
strategy:
15+
matrix:
16+
include:
17+
- {shared: ON}
18+
- {shared: OFF}
19+
steps:
20+
- uses: actions/checkout@v3
21+
- name: Use cmake
22+
run: |
23+
mkdir build &&
24+
cd build &&
25+
cmake .. &&
26+
cmake --build .

.github/workflows/vs.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: VS17-CI
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
ci:
7+
name: windows-vs17
8+
runs-on: windows-latest
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
include:
13+
- {gen: Visual Studio 17 2022, arch: x64, shared: ON}
14+
- {gen: Visual Studio 17 2022, arch: x64, shared: OFF}
15+
steps:
16+
- name: checkout
17+
uses: actions/checkout@v3
18+
- name: Configure
19+
run: |
20+
cmake -G "${{matrix.gen}}" -A ${{matrix.arch}} -B build
21+
- name: Build Debug
22+
run: cmake --build build --config Debug --verbose
23+
- name: Build Release
24+
run: cmake --build build --config Release --verbose

CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
cmake_minimum_required(VERSION 3.15)
2+
3+
project(SimpleFastFloatBenchmark VERSION 0.1.0 LANGUAGES CXX C)
4+
set(CMAKE_CXX_STANDARD 17)
5+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
6+
set(CMAKE_C_STANDARD 99)
7+
set(CMAKE_C_STANDARD_REQUIRED ON)
8+
9+
if (NOT CMAKE_BUILD_TYPE)
10+
message(STATUS "No build type selected, default to Release")
11+
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
12+
endif()
13+
include(cmake/CPM.cmake)
14+
add_subdirectory(dependencies)
15+
16+
add_subdirectory(benchmarks)

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# float_serialization_benchmark
2+
3+
This repository contains benchmarking code for
4+
5+
6+
If you have a recent version of CMake (3.15 or better) under linux, you can simply
7+
go in the directory and type the following commands:
8+
9+
```
10+
cmake -B build .
11+
cmake --build build
12+
./build/benchmarks/benchmark
13+
```
14+
15+
16+
17+
You may use docker to run these benchmarks easily on a variety of platforms: see https://github.com/lemire/docker_programming_station
18+
19+
20+
## Windows
21+
22+
Usage under Windows is similar. After installing cmake and Visual Studio 2019, one might type in the appropriate shell:
23+
24+
```
25+
cmake -B build .
26+
cmake --build build --config Release
27+
.\build\benchmarks\Release\benchmark.exe
28+
```
29+
30+
## Advanced Usage
31+
32+
Serialize the strings (one per line) included in a text file:
33+
34+
```
35+
./build/benchmarks/benchmark -f data/canada.txt
36+
```
37+
38+
Serialize strings generated from floats in (0,1):
39+
40+
41+
```
42+
./build/benchmarks/benchmark
43+
```
44+
45+
## References
46+
47+
- [dtoa Benchmark](https://github.com/miloyip/dtoa-benchmark)
48+
- [Benchmark different approaches to parsing scientific datafiles](https://github.com/alugowski/parse-bench)

benchmarks/CMakeLists.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
add_executable(benchmark benchmark.cpp)
2+
target_link_libraries(benchmark PUBLIC grisu2)
3+
if (NOT CYGWIN)
4+
target_link_libraries(benchmark PUBLIC absl::strings)
5+
endif()
6+
target_link_libraries(benchmark PUBLIC double-conversion)
7+
target_link_libraries(benchmark PUBLIC ryu::ryu)
8+
target_link_libraries(benchmark PUBLIC fmt)
9+
target_link_libraries(benchmark PUBLIC cxxopts)
10+
target_link_libraries(benchmark PUBLIC netlib)
11+
target_link_libraries(benchmark PUBLIC dragonbox::dragonbox_to_chars)
12+
13+
include(CheckSourceCompiles)
14+
15+
check_source_compiles(CXX "
16+
#include <charconv>
17+
int main(void) {
18+
double value = 0;
19+
const char* ptr;
20+
std::from_chars_result result = std::from_chars(ptr, ptr, value, std::chars_format::general);
21+
return 0;
22+
}
23+
" from_chars_double)
24+
25+
if (from_chars_double)
26+
target_compile_definitions(benchmark PUBLIC FROM_CHARS_DOUBLE_SUPPORTED=1)
27+
endif()

benchmarks/benchmark.cpp

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/**
2+
* Currently, we benchmark the speed of converting floating-point numbers to
3+
* strings. We compare the speed of different libraries and methods. The
4+
* benchmark is run on a file containing one floating-point number per line or
5+
* synthetic data. The synthetic data is generated using a random number
6+
* generator. We measure the speed of the conversion by computing the volume of
7+
* data generated.
8+
*
9+
* Todo: add more libraries and methods.
10+
*
11+
* Todo: Verify the results and make sure we are comparing apples to apples. In
12+
* particular, we should make sure that the conversion is correct and that the
13+
* strings produced are similar. We could have different benchmarks depending on
14+
* the precision of the conversion.
15+
*
16+
* Todo: We are currently only using doubles, we should also test with floats.
17+
*/
18+
19+
#ifndef __CYGWIN__
20+
#include "absl/strings/numbers.h"
21+
#include "absl/strings/str_cat.h"
22+
#endif
23+
24+
#include "dragonbox/dragonbox_to_chars.h"
25+
#include "ryu/ryu.h"
26+
27+
#include "double-conversion/double-conversion.h"
28+
#include "double-conversion/ieee.h"
29+
30+
#define IEEE_8087
31+
#include "benchutil.h"
32+
#include "cxxopts.hpp"
33+
#include "gdtoa.h"
34+
#include "grisu2.h"
35+
#include "random_generators.h"
36+
#include <algorithm>
37+
#include <charconv>
38+
#include <chrono>
39+
#include <climits>
40+
#include <cmath>
41+
#include <cstdint>
42+
#include <cstdio>
43+
#include <cstdlib>
44+
#include <cstring>
45+
#include <ctype.h>
46+
#include <float.h>
47+
#include <fmt/format.h>
48+
#include <fstream>
49+
#include <iomanip>
50+
#include <iostream>
51+
#include <limits.h>
52+
#include <locale.h>
53+
#include <random>
54+
#include <sstream>
55+
#include <stdio.h>
56+
#include <string>
57+
#include <vector>
58+
59+
#if FROM_CHARS_DOUBLE_SUPPORTED
60+
#include <charconv>
61+
#endif
62+
63+
void process(std::vector<double> &lines) {
64+
size_t repeat = 100;
65+
pretty_print(lines, "std::to_string", [](std::vector<double> &lines) {
66+
double volume = 0;
67+
for (auto d : lines) {
68+
std::string s = std::to_string(d);
69+
volume += s.size();
70+
}
71+
return volume;
72+
});
73+
pretty_print(lines, "fmt::format", [](std::vector<double> &lines) {
74+
double volume = 0;
75+
for (auto d : lines) {
76+
std::string s = fmt::format("{}", d);
77+
volume += s.size();
78+
}
79+
return volume;
80+
});
81+
pretty_print(
82+
lines, "netlib",
83+
[](std::vector<double> &lines) {
84+
double volume = 0;
85+
char *result;
86+
int decpt, sign;
87+
char *rve;
88+
for (auto d : lines) {
89+
char *result = dtoa(d, 0, 0, &decpt, &sign, &rve);
90+
if (result) {
91+
volume += (rve - result);
92+
freedtoa(result);
93+
} else {
94+
std::cerr << "problem with " << d << std::endl;
95+
std::abort();
96+
}
97+
}
98+
return volume;
99+
},
100+
10);
101+
pretty_print(lines, "sprintf", [](std::vector<double> &lines) {
102+
double volume = 0;
103+
char buffer[100];
104+
for (auto d : lines) {
105+
volume += snprintf(buffer, sizeof(buffer), "%g", d);
106+
}
107+
return volume;
108+
});
109+
pretty_print(lines, "grisu2", [](std::vector<double> &lines) {
110+
double volume = 0;
111+
char buffer[100];
112+
for (auto d : lines) {
113+
char *newp = grisu2::to_chars(buffer, nullptr, d);
114+
volume += newp - buffer;
115+
}
116+
return volume;
117+
});
118+
#if FROM_CHARS_DOUBLE_SUPPORTED
119+
pretty_print(lines, "std::to_chars", [](std::vector<double> &lines) {
120+
double volume = 0;
121+
char buffer[100];
122+
for (auto d : lines) {
123+
auto [p, ec] = std::to_chars(buffer, buffer + sizeof(buffer), d);
124+
if(ec != std::errc()) {
125+
std::cerr << "problem with " << d << std::endl;
126+
std::abort();
127+
}
128+
volume += p - buffer;
129+
}
130+
return volume;
131+
});
132+
#else
133+
std::cout << "# std::to_chars not supported" << std::endl;
134+
#endif
135+
pretty_print(lines, "dragonbox", [](std::vector<double> &lines) {
136+
double volume = 0;
137+
char buffer[100];
138+
for (auto d : lines) {
139+
char *end_ptr = jkj::dragonbox::to_chars(d, buffer);
140+
volume += end_ptr - &buffer[0];
141+
}
142+
return volume;
143+
});
144+
145+
pretty_print(lines, "double_conversion", [](std::vector<double> &lines) {
146+
double volume = 0;
147+
double_conversion::DoubleToStringConverter converter(
148+
double_conversion::DoubleToStringConverter::NO_FLAGS, "inf", "nan", 'e',
149+
-4, 6, 0, 0);
150+
const int kBufferSize = 100;
151+
char buffer[kBufferSize];
152+
double_conversion::StringBuilder builder(buffer, kBufferSize);
153+
154+
for (auto d : lines) {
155+
builder.Reset();
156+
if (!converter.ToShortest(d, &builder)) {
157+
std::cerr << "problem with " << d << std::endl;
158+
std::abort();
159+
}
160+
volume += strlen(builder.Finalize());
161+
}
162+
return volume;
163+
});
164+
165+
pretty_print(lines, "abseil", [](std::vector<double> &lines) {
166+
double volume = 0;
167+
std::string buffer;
168+
for (auto d : lines) {
169+
buffer.clear();
170+
absl::StrAppend(&buffer, d);
171+
volume += buffer.size();
172+
}
173+
return volume;
174+
});
175+
}
176+
177+
void fileload(const char *filename) {
178+
std::ifstream inputfile(filename);
179+
if (!inputfile) {
180+
std::cerr << "can't open " << filename << std::endl;
181+
return;
182+
}
183+
std::string line;
184+
std::vector<double> lines;
185+
lines.reserve(10000); // let us reserve plenty of memory.
186+
while (getline(inputfile, line)) {
187+
try {
188+
lines.push_back(std::stod(line));
189+
} catch (...) {
190+
std::cerr << "problem with " << line << std::endl;
191+
std::cerr << "We expect floating-point numbers (one per line)."
192+
<< std::endl;
193+
std::abort();
194+
}
195+
}
196+
std::cout << "# read " << lines.size() << " lines " << std::endl;
197+
process(lines);
198+
}
199+
200+
void parse_random_numbers(size_t howmany, std::string random_model) {
201+
std::cout << "# parsing random numbers" << std::endl;
202+
std::vector<double> lines;
203+
auto g = get_generator_by_name(random_model);
204+
std::cout << "model: " << g->describe() << std::endl;
205+
std::cout << "volume: " << howmany << " floats" << std::endl;
206+
lines.reserve(howmany); // let us reserve plenty of memory.
207+
for (size_t i = 0; i < howmany; i++) {
208+
double line = g->new_float();
209+
lines.push_back(line);
210+
}
211+
process(lines);
212+
}
213+
214+
cxxopts::Options
215+
options("benchmark",
216+
"Compute the parsing speed of different number parsers.");
217+
218+
int main(int argc, char **argv) {
219+
try {
220+
options.add_options()("f,file", "File name.",
221+
cxxopts::value<std::string>()->default_value(""))(
222+
"v,volume", "Volume (number of floats generated).",
223+
cxxopts::value<size_t>()->default_value("100000"))(
224+
"m,model", "Random Model.",
225+
cxxopts::value<std::string>()->default_value("uniform"))(
226+
"h,help", "Print usage.");
227+
auto result = options.parse(argc, argv);
228+
if (result["help"].as<bool>()) {
229+
std::cout << options.help() << std::endl;
230+
return EXIT_SUCCESS;
231+
}
232+
auto filename = result["file"].as<std::string>();
233+
if (filename.empty()) {
234+
parse_random_numbers(result["volume"].as<size_t>(),
235+
result["model"].as<std::string>());
236+
std::cout << "# You can also provide a filename (with the -f flag): it "
237+
"should contain one "
238+
"string per line corresponding to a number"
239+
<< std::endl;
240+
} else {
241+
fileload(filename.c_str());
242+
}
243+
} catch (const std::exception &e) {
244+
std::cout << "error parsing options: " << e.what() << std::endl;
245+
return EXIT_FAILURE;
246+
}
247+
}

0 commit comments

Comments
 (0)