// (C) Copyright Stephan T. Lavavej 2019 - 2023. // (C) Copyright Matt Borland 2023. // Use, modification and distribution are subject to the // Boost Software License, Version 1.0. (See accompanying file // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // // Derived from mailing list post: https://lists.boost.org/Archives/boost/2023/05/254660.php #ifndef _MSC_VER #define AVOID_SPRINTF_S #endif // _MSC_VER #ifndef AVOID_CHARCONV #include #endif // AVOID_CHARCONV #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace std::chrono; void verify(const bool b) { if (!b) { puts("VERIFICATION FAILURE"); exit(EXIT_FAILURE); } } enum class RoundTrip { Sci, Fix, Gen, Hex, Lossy, u32, u64 }; constexpr size_t N = 2'000'000; // how many values to test constexpr size_t K = 5; // how many times to repeat the test, for cleaner timing constexpr size_t BufSize = 2'000; // more than enough unsigned int global_dummy = 0; template void test_boost_spirit_karma(const char* const str, const vector& vec) { namespace karma = boost::spirit::karma; const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { for (const auto& elem : vec) { char buffer[BufSize]; char* it = buffer; if constexpr (std::is_same_v) { karma::generate(it, karma::float_, elem); } else { karma::generate(it, karma::double_, elem); } } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); } template void test_lexical_cast(const char* const str, const vector& vec) { const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { for (const auto& elem : vec) { const auto ret = boost::lexical_cast>(elem); // "-1.2345678901234567e-100" plus null term global_dummy += static_cast(ret[0]); } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); } template int sprintf_wrapper(char (&buf)[BufSize], const char* const fmt, const Floating elem) { #ifdef AVOID_SPRINTF_S return snprintf(buf, sizeof(buf), fmt, elem); #else // AVOID_SPRINTF_S return sprintf_s(buf, BufSize, fmt, elem); #endif // AVOID_SPRINTF_S } template void test_sprintf(const char* const str, const vector& vec, const char* const fmt) { char buf[BufSize]; const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { for (const auto& elem : vec) { const int ret = sprintf_wrapper(buf, fmt, elem); global_dummy += static_cast(ret); global_dummy += static_cast(buf[0]); } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); for (const auto& elem : vec) { verify(sprintf_wrapper(buf, fmt, elem) != -1); if constexpr (RT == RoundTrip::Lossy) { // skip lossy conversions } else if constexpr (is_same_v) { verify(strtof(buf, nullptr) == elem); } else { verify(strtod(buf, nullptr) == elem); } } } #ifndef AVOID_CHARCONV constexpr chars_format chars_format_from_RoundTrip(const RoundTrip rt) { switch (rt) { case RoundTrip::Sci: return chars_format::scientific; case RoundTrip::Fix: return chars_format::fixed; case RoundTrip::Gen: return chars_format::general; case RoundTrip::Hex: return chars_format::hex; case RoundTrip::Lossy: default: puts("CHARS FORMAT FAIL"); exit(EXIT_FAILURE); } } constexpr boost::charconv::chars_format boost_chars_format_from_RoundTrip(const RoundTrip rt) { switch (rt) { case RoundTrip::Sci: return boost::charconv::chars_format::scientific; case RoundTrip::Fix: return boost::charconv::chars_format::fixed; case RoundTrip::Gen: return boost::charconv::chars_format::general; case RoundTrip::Hex: return boost::charconv::chars_format::hex; case RoundTrip::Lossy: default: puts("CHARS FORMAT FAIL"); exit(EXIT_FAILURE); } } template void test_STL_to_chars(const char* const str, const vector& vec, const Args&... args) { char buf[BufSize]; const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { for (const auto& elem : vec) { const auto result = to_chars(buf, buf + BufSize, elem, args...); global_dummy += static_cast(result.ptr - buf); global_dummy += static_cast(buf[0]); } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); for (const auto& elem : vec) { const auto result = to_chars(buf, buf + BufSize, elem, args...); verify(result.ec == errc{}); if constexpr (RT == RoundTrip::Lossy) { // skip lossy conversions } else { Floating round_trip; const auto from_result = std::from_chars(buf, result.ptr, round_trip, chars_format_from_RoundTrip(RT)); verify(from_result.ec == errc{}); verify(from_result.ptr == result.ptr); verify(round_trip == elem); } } } template void test_boost_to_chars(const char* const str, const vector& vec, const Args&... args) { char buf[BufSize] {}; const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { for (const auto& elem : vec) { const auto result = to_chars(buf, buf + BufSize, elem, args...); global_dummy += static_cast(result.ptr - buf); global_dummy += static_cast(buf[0]); } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); for (const auto& elem : vec) { const auto result = to_chars(buf, buf + BufSize, elem, args...); if (result.ec != errc()) { std::cerr << "To chars failure with: " << elem << "\n to_chars value: " << buf << std::endl; } if constexpr (RT == RoundTrip::Lossy) { // skip lossy conversions } else { Floating round_trip; const auto from_result = boost::charconv::from_chars(buf, result.ptr, round_trip, boost_chars_format_from_RoundTrip(RT)); if (from_result.ec != errc() || from_result.ptr != result.ptr || round_trip != elem) { std::cerr << std::setprecision(std::numeric_limits::digits10 + 1) << "Roundtrip failure with: " << elem << "\n to_chars val: " << buf << "\n from_chars val: " << round_trip << "\n from_chars ptr: " << static_cast(from_result.ptr - buf) << "\n to_chars ptr: " << static_cast(result.ptr - buf) << std::endl; } } } } #endif // AVOID_CHARCONV template vector prepare_strings(const vector& vec) { vector output; char buf[BufSize]; for (const auto& elem : vec) { int ret; if constexpr (RT == RoundTrip::Sci) { if constexpr (is_same_v) { ret = sprintf_wrapper(buf, "%.8e", elem); } else { ret = sprintf_wrapper(buf, "%.16e", elem); } } else if constexpr (RT == RoundTrip::Hex) { if constexpr (is_same_v) { ret = sprintf_wrapper(buf, "%.6a", elem); } else { ret = sprintf_wrapper(buf, "%.13a", elem); } } else if constexpr (RT == RoundTrip::u32) { ret = sprintf_wrapper(buf, "%lu", elem); } else { static_assert(RT == RoundTrip::u64); ret = sprintf_wrapper(buf, "%llu", elem); } verify(ret != -1); output.insert(output.end(), buf, buf + ret + 1); // include null terminator } return output; } template void test_strtox(const char* const str, const vector& original, const vector& strings) { vector round_trip(N); const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* ptr = strings.data(); char* endptr = nullptr; for (size_t n = 0; n < N; ++n) { if constexpr (is_same_v) { round_trip[n] = strtof(ptr, &endptr); } else { round_trip[n] = strtod(ptr, &endptr); } ptr = endptr + 1; // advance past null terminator } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); verify(round_trip == original); } #ifndef AVOID_CHARCONV vector erase_0x(const vector& strings) { vector output; output.reserve(strings.size() - 2 * N); for (auto i = strings.begin(); i != strings.end();) { if (*i == '-') { output.push_back('-'); i += 3; // advance past "-0x"; } else { i += 2; // advance past "0x"; } for (;;) { const char c = *i++; output.push_back(c); if (c == '\0') { break; } } } return output; } template void test_from_chars(const char* const str, const vector& original, const vector& strings) { const char* const last = strings.data() + strings.size(); vector round_trip(N); const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* first = strings.data(); for (size_t n = 0; n < N; ++n) { const auto from_result = from_chars(first, last, round_trip[n], chars_format_from_RoundTrip(RT)); first = from_result.ptr + 1; // advance past null terminator } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); verify(round_trip == original); } template void test_from_chars_integer(const char* const str, const vector& original, const vector& strings) { const char* const last = strings.data() + strings.size(); vector round_trip(N); const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* first = strings.data(); for (size_t n = 0; n < N; ++n) { const auto from_result = from_chars(first, last, round_trip[n], base); first = from_result.ptr + 1; // advance past null terminator } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); verify(round_trip == original); } template void test_boost_from_chars_integer(const char* const str, const vector& original, const vector& strings) { const char* const last = strings.data() + strings.size(); vector round_trip(N); const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* first = strings.data(); for (size_t n = 0; n < N; ++n) { const auto from_result = boost::charconv::from_chars(first, last, round_trip[n], base); first = from_result.ptr + 1; // advance past null terminator } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); verify(round_trip == original); } template void test_boost_from_chars(const char* const str, const vector& original, const vector& strings) { const char* const last = strings.data() + strings.size(); vector round_trip(N); const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* first = strings.data(); for (size_t n = 0; n < N; ++n) { const auto from_result = boost::charconv::from_chars(first, last, round_trip[n], boost_chars_format_from_RoundTrip(RT)); first = from_result.ptr + 1; // advance past null terminator } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); verify(round_trip == original); } template bool parse_numbers(Iterator first, Iterator last, std::vector& v) { namespace qi = boost::spirit::qi; namespace ascii = boost::spirit::ascii; namespace phoenix = boost::phoenix; using qi::double_; using qi::float_; using qi::phrase_parse; using qi::_1; using ascii::space; using phoenix::push_back; bool r = false; if constexpr (std::is_same_v) { r = phrase_parse(first, last, // Begin grammar ( float_[push_back(phoenix::ref(v), _1)] >> *('\0' >> float_[push_back(phoenix::ref(v), _1)]) ) , // End grammar space); } else { static_assert(std::is_same_v); r = phrase_parse(first, last, // Begin grammar ( double_[push_back(phoenix::ref(v), _1)] >> *('\0' >> double_[push_back(phoenix::ref(v), _1)]) ) , // End grammar space); } if (first != last) // fail if we did not get a full match { return false; } return r; } template void test_boost_spirit_qi(const char* const str, const vector& /*original*/, const vector& strings) { const char* const last = strings.data() + strings.size(); vector round_trip(N); const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* first = strings.data(); string test_str(first, static_cast(last - first)); parse_numbers(test_str.begin(), test_str.end(), round_trip); } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); // verify(round_trip == original); } /* template void test_boost_from_chars_parser(const char* const str, const vector&, const vector& strings) { const char* const last = strings.data() + strings.size(); vector round_trip(N); bool sign {}; std::uint64_t significand {}; std::int64_t exponent {}; const auto start = steady_clock::now(); for (size_t k = 0; k < K; ++k) { const char* first = strings.data(); for (size_t n = 0; n < N; ++n) { const auto from_result = boost::charconv::detail::parser(first, last, sign, significand, exponent, boost_chars_format_from_RoundTrip(RT)); first = from_result.ptr + 1; // advance past null terminator } } const auto finish = steady_clock::now(); printf("%6.1f ns | %s\n", duration{finish - start}.count() / (N * K), str); // verify(round_trip == original); } */ #endif // AVOID_CHARCONV void test_all() { #if defined(__clang__) && defined(_M_IX86) const char* const toolset = "Clang/LLVM x86 + MSVC STL"; #elif defined(__clang__) && defined(_M_X64) const char* const toolset = "Clang/LLVM x64 + MSVC STL"; #elif !defined(__clang__) && defined(_M_IX86) const char* const toolset = "C1XX/C2 x86 + MSVC STL"; #elif !defined(__clang__) && defined(_M_X64) const char* const toolset = "C1XX/C2 x64 + MSVC STL"; #elif defined(__clang__) && defined(__APPLE__) && defined(__arm64__) const char* const toolset = "Clang/LLVM Apple ARM + libstdc++"; #elif !defined(__clang__) && defined(__APPLE__) && defined(__arm64__) const char* const toolset = "GCC Apple ARM + libstdc++"; #elif defined(__clang__) && defined(__x86_64__) const char* const toolset = "Clang/LLVM " BOOST_STRINGIZE(__clang_major__) " x64 + libstdc++" #elif defined(__GNUC__) && defined(__x86_64__) const char* const toolset = "GCC " BOOST_STRINGIZE(__GNUC__) " x64 + libstdc++" ; #else const char* const toolset = "Unknown Toolset"; #endif puts(toolset); vector vec_flt; vector vec_dbl; vector vec_u32; vector vec_u64; { mt19937_64 mt64; vec_flt.reserve(N); while (vec_flt.size() < N) { const uint32_t val = static_cast(mt64()); constexpr uint32_t inf_nan = 0x7F800000U; if ((val & inf_nan) == inf_nan) { continue; // skip INF/NAN } float flt; static_assert(sizeof(flt) == sizeof(val)); memcpy(&flt, &val, sizeof(flt)); vec_flt.push_back(flt); } vec_dbl.reserve(N); while (vec_dbl.size() < N) { const uint64_t val = mt64(); constexpr uint64_t inf_nan = 0x7FF0000000000000ULL; if ((val & inf_nan) == inf_nan) { continue; // skip INF/NAN } double dbl; static_assert(sizeof(dbl) == sizeof(val)); memcpy(&dbl, &val, sizeof(dbl)); vec_dbl.push_back(dbl); } vec_u32.reserve(N); while (vec_u32.size() < N) { vec_u32.emplace_back(static_cast(mt64())); } vec_u64.reserve(N); while (vec_u64.size() < N) { vec_u64.emplace_back(mt64()); } } test_lexical_cast("Boost.lexical_cast float", vec_flt); test_lexical_cast("Boost.lexical_cast double", vec_dbl); test_boost_spirit_karma("Boost.spirit.karma float", vec_flt); test_boost_spirit_karma("Boost.spirit.karma double", vec_dbl); test_sprintf("std::sprintf float plain shortest", vec_flt, "%.g"); test_sprintf("std::sprintf double plain shortest", vec_dbl, "%.g"); /* test_sprintf("std::sprintf float scientific 8", vec_flt, "%.8e"); test_sprintf("std::sprintf double scientific 16", vec_dbl, "%.16e"); test_sprintf("std::sprintf float fixed 6 (lossy)", vec_flt, "%f"); test_sprintf("std::sprintf double fixed 6 (lossy)", vec_dbl, "%f"); test_sprintf("std::sprintf float general 9", vec_flt, "%.9g"); test_sprintf("std::sprintf double general 17", vec_dbl, "%.17g"); test_sprintf("std::sprintf float hex 6", vec_flt, "%.6a"); test_sprintf("std::sprintf double hex 13", vec_dbl, "%.13a"); */ #ifndef AVOID_CHARCONV test_STL_to_chars("std::to_chars float plain shortest", vec_flt); test_STL_to_chars("std::to_chars double plain shortest", vec_dbl); test_boost_to_chars("Boost.Charconv::to_chars float plain shortest", vec_flt); test_boost_to_chars("Boost.Charconv::to_chars double plain shortest", vec_dbl); /* test_STL_to_chars("std::to_chars float scientific shortest", vec_flt, chars_format::scientific); test_STL_to_chars("std::to_chars double scientific shortest", vec_dbl, chars_format::scientific); test_boost_to_chars("Boost.Charconv::to_chars float scientific shortest", vec_flt, boost::charconv::chars_format::scientific); test_boost_to_chars("Boost.Charconv::to_chars double scientific shortest", vec_dbl, boost::charconv::chars_format::scientific); test_STL_to_chars("std::to_chars float fixed shortest", vec_flt, chars_format::fixed); test_STL_to_chars("std::to_chars double fixed shortest", vec_dbl, chars_format::fixed); test_boost_to_chars("Boost.Charconv::to_chars float fixed shortest", vec_flt, boost::charconv::chars_format::fixed); test_boost_to_chars("Boost.Charconv::to_chars double fixed shortest", vec_dbl, boost::charconv::chars_format::fixed); test_STL_to_chars("std::to_chars float general shortest", vec_flt, chars_format::general); test_STL_to_chars("std::to_chars double general shortest", vec_dbl, chars_format::general); test_boost_to_chars("Boost.Charconv::to_chars float general shortest", vec_flt, boost::charconv::chars_format::general); test_boost_to_chars("Boost.Charconv::to_chars double general shortest", vec_dbl, boost::charconv::chars_format::general); test_STL_to_chars("std::to_chars float hex shortest", vec_flt, chars_format::hex); test_STL_to_chars("std::to_chars double hex shortest", vec_dbl, chars_format::hex); test_boost_to_chars("Boost.Charconv::to_chars float hex shortest", vec_flt, boost::charconv::chars_format::hex); test_boost_to_chars("Boost.Charconv::to_chars double hex shortest", vec_dbl, boost::charconv::chars_format::hex); test_STL_to_chars("std::to_chars float scientific 8", vec_flt, chars_format::scientific, 8); test_STL_to_chars("std::to_chars double scientific 16", vec_dbl, chars_format::scientific, 16); test_boost_to_chars("Boost.Charconv::to_chars float scientific 8", vec_flt, boost::charconv::chars_format::scientific, 8); test_boost_to_chars("Boost.Charconv::to_chars double scientific 16", vec_dbl, boost::charconv::chars_format::scientific, 16); test_STL_to_chars("std::to_chars float fixed 6 (lossy)", vec_flt, chars_format::fixed, 6); test_STL_to_chars("std::to_chars double fixed 6 (lossy)", vec_dbl, chars_format::fixed, 6); test_boost_to_chars("Boost.Charconv::to_chars float fixed 6 (lossy)", vec_flt, boost::charconv::chars_format::fixed, 6); test_boost_to_chars("Boost.Charconv::to_chars double fixed 6 (lossy)", vec_dbl, boost::charconv::chars_format::fixed, 6); test_STL_to_chars("std::to_chars float general 9", vec_flt, chars_format::general, 9); test_STL_to_chars("std::to_chars double general 17", vec_dbl, chars_format::general, 17); test_boost_to_chars("Boost.Charconv::to_chars float general 9", vec_flt, chars_format::general, 9); test_boost_to_chars("Boost.Charconv::to_chars double general 17", vec_dbl, chars_format::general, 17); test_STL_to_chars("std::to_chars float hex 6", vec_flt, chars_format::hex, 6); test_STL_to_chars("std::to_chars double hex 13", vec_dbl, chars_format::hex, 13); test_boost_to_chars("Boost.Charconv::to_chars float hex 6", vec_flt, chars_format::hex, 6); test_boost_to_chars("Boost.Charconv::to_chars double hex 13", vec_dbl, chars_format::hex, 13); */ #endif // AVOID_CHARCONV puts("----------"); const vector strings_sci_flt = prepare_strings(vec_flt); const vector strings_sci_dbl = prepare_strings(vec_dbl); const vector strings_hex_flt = prepare_strings(vec_flt); const vector strings_hex_dbl = prepare_strings(vec_dbl); const vector strings_u64 = prepare_strings(vec_u64); test_strtox("std::strtof float scientific", vec_flt, strings_sci_flt); test_strtox("std::strtod double scientific", vec_dbl, strings_sci_dbl); //test_strtox("std::strtof float hex", vec_flt, strings_hex_flt); //test_strtox("std::strtod double hex", vec_dbl, strings_hex_dbl); test_from_chars("std::from_chars float scientific", vec_flt, strings_sci_flt); test_from_chars("std::from_chars double scientific", vec_dbl, strings_sci_dbl); test_boost_from_chars("Boost.Charconv::from_chars float scientific", vec_flt, strings_sci_flt); test_boost_from_chars("Boost.Charconv::from_chars double scientific", vec_dbl, strings_sci_dbl); test_boost_spirit_qi("Boost.Spirit.Qi float scientific", vec_flt, strings_sci_flt); test_boost_spirit_qi("Boost.Spirit.Qi double scientific", vec_dbl, strings_sci_dbl); //test_boost_from_chars_parser("Boost.Charconv::from_chars::parser float scientific", vec_flt, strings_sci_flt); //test_boost_from_chars_parser("Boost.Charconv::from_chars::parser double scientific", vec_dbl, strings_sci_dbl); test_from_chars_integer<10>("std::from_chars uint64_t", vec_u64, strings_u64); test_boost_from_chars_integer<10>("Boost.Charconv::from_chars uint64_t", vec_u64, strings_u64); //test_from_chars("std::from_chars float hex", vec_flt, erase_0x(strings_hex_flt)); //test_from_chars("std::from_chars double hex", vec_dbl, erase_0x(strings_hex_dbl)); //test_boost_from_chars("Boost.Charconv::from_chars float hex", vec_flt, erase_0x(strings_hex_flt)); //test_boost_from_chars("Boost.Charconv::from_chars double hex", vec_dbl, erase_0x(strings_hex_dbl)); printf("global_dummy: %u\n", global_dummy); } #ifdef BOOST_CHARCONV_RUN_BENCHMARKS int main() { try { test_all(); } catch (const exception& e) { printf("Exception: %s\n", e.what()); } catch (...) { printf("Unknown exception.\n"); } return 1; } #else int main() { return 0; } #endif