This is a fork of Milo Yip's dtoa-benchmark with the following changes:
- CMake support
- Fixed reporting of results
- Added {fmt}
- Added Dragonbox
- Added Schubfach
- Removed the use of deprecated
strstream - Disabled Grisu2 implementations since they don't guarantee correctness
Copyright(c) 2014 Milo Yip (miloyip@gmail.com)
This benchmark evaluates the performance of conversion from double precision
IEEE-754 floating point (double) to ASCII string. The function prototype is:
void dtoa(double value, char* buffer);The character string result must be convertible to the original value
exactly via some correct implementation of strtod, i.e. roundtrip
convertible.
Note that dtoa is not a standard function in C and C++.
Firstly the program verifies the correctness of implementations.
Then, one case for benchmark is carried out:
- RandomDigit: Generates 1000 random
doublevalues, filtered out+/-infandnan. Then convert them to limited precision (1 to 17 decimal digits in significand). Finally convert these numbers into ASCII.
Each digit group is run for 100 times. The minimum time duration is measured for 10 trials.
- Configure:
cmake . - Build and run benchmark:
make run-benchmark
The results in CSV format will be written to the file
result/<cpu>_<os>_<compiler>.csv and automatically converted to HTML with
the same base name and the .html extension.
The following are results measured on a MacBook Pro (Apple M1 Pro), where
dtoa is compiled by Apple clang 17.0.0 (clang-1700.0.13.5) and run on macOS.
The speedup is based on sprintf.
| Function | Time (ns) | Speedup |
|---|---|---|
| ostringstream | 875.625 | 1.00x |
| sprintf | 735.428 | 1.19x |
| doubleconv | 83.249 | 10.52x |
| ryu | 37.163 | 23.56x |
| schubfach | 24.555 | 35.66x |
| fmt | 22.360 | 39.16x |
| dragonbox | 20.790 | 42.12x |
| null | 0.936 | 935.03x |
Notes:
- The
nullimplementation does nothing. It measures the overheads of looping and function call. sprintfandostringstreamdon't generate the shortest representation, e.g.0.1is formatted as0.10000000000000001.ryu,dragonboxandschubfachonly produce the output in the exponential format, e.g.0.1is formatted as1E-1or similar.
Some results of various configurations are located at result. They can be
accessed online, with interactivity provided by Google Charts:
| Function | Description |
|---|---|
| ostringstream | std::ostringstream in C++ standard library with setprecision(17). |
| sprintf | sprintf in C standard library with "%.17g" format. |
| doubleconv | C++ implementation extracted from Google's V8 JavaScript Engine with EcmaScriptConverter().ToShortest() (based on Grisu3, fall back to slower bignum algorithm when Grisu3 failed to produce shortest implementation). |
| fmt | fmt::format_to with format string compilation (implements Dragonbox). |
| dragonbox | jkj::dragonbox::to_chars with full tables. |
| schubfach | Schubfach implementation in C++ |
| null | Do nothing. |
Notes:
std::to_string is not tested as it does not fulfill the roundtrip
requirement (until C++26).
-
How to add an implementation?
You may clone an existing implementation file. And then modify it and add to the CMake config. Note that it will automatically register to the benchmark by macro
REGISTER_TEST(name).Making a pull request of new implementations is welcome.
-
Why not converting
doubletostd::string?It may introduce heap allocation, which is a big overhead. User can easily wrap these low-level functions to return
std::string, if needed. -
Why fast
dtoafunctions is needed?They are a very common operations in writing data in text format. The standard way of
sprintf,std::stringstream, often provides poor performance. The author of this benchmark would optimize thesprintfimplementation in RapidJSON, thus he creates this project.

