snitch
snitch copied to clipboard
Use `std::to_chars` instead of `std::snprintf` for converting numbers to string
std::snprintf insists on adding a null-terminating character all the time, which forces us to loose one character when running out of space. This is normally not a problem, because we will usually truncate with ... anyway (hence overwriting the last three characters), but just out of principle.
std::to_chars is the modern alternative. I haven't seen benchmarks to compare the performance, but I would expect it to be faster (edit: it is massively faster). The downside of std::to_chars is that it won't write anything if there's not enough space, so we need to handle that logic ourselves. The other big downside is that std::to_chars for floating point numbers has poor vendor support, and was added only in libstdc++ 11, libc++ 14 despite being a C++17 feature (MSVC did better there).
Work in progress on this is started in https://github.com/cschreib/snatch/tree/to_chars.
As an alternative, we now have our own constexpr-friendly code to do number-to-string conversions. On clang++-17 with libc++-17, I get the following relative performance (lower is better):
| Debug | Release | |
|---|---|---|
std::snprintf |
1 | 1 |
std::to_chars |
0.14 | 0.085 |
snitch::append_constexpr |
2.8 | 0.39 |
In Debug the comparison isn't very fair, since snitch::append_constexpr is fully un-optimised, while the other two are taken pre-compiled from an optimised build of libc++. But although unfair, that's the reality of how it would be used in practice. This shows that our implementation is 3x slower than std::snprintf, and 20x slower than std::to_chars.
In Release the comparison is fair. We're now 3x faster than std::snprintf, but still 5x slower than std::to_chars.
So the tradeoff of using snitch::append_constexpr instead of std::snprintf would be worse perf in Debug, and better perf in Release. Clearly, std::to_chars would be the best though (sadly not constexpr for floating point numbers yet).
Using the following for std::to_chars:
template<typename T>
bool append_to_chars(snitch::small_string_span ss, T value) noexcept {
auto [end, err] = std::to_chars(
ss.end(), ss.begin() + ss.capacity(), value, std::chars_format::scientific, 6);
if (err != std::errc{}) {
snitch::small_string<32> fallback;
auto [end2, err2] = std::to_chars(
fallback.end(), fallback.begin() + fallback.capacity(), value,
std::chars_format::scientific, 6);
if (err2 != std::errc{}) {
return false;
}
fallback.grow(end2 - fallback.end());
return append(ss, fallback);
}
ss.grow(end - ss.end());
return true;
}
Completed in #160.