snitch icon indicating copy to clipboard operation
snitch copied to clipboard

Use `std::to_chars` instead of `std::snprintf` for converting numbers to string

Open cschreib opened this issue 2 years ago • 1 comments

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.

cschreib avatar Nov 03 '22 23:11 cschreib

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;
}

cschreib avatar May 01 '23 17:05 cschreib

Completed in #160.

cschreib avatar May 02 '24 15:05 cschreib