From e7de3ef73ed354966b010369d0b30841b2ba71de Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Mon, 2 Jun 2025 12:46:17 -0400 Subject: [PATCH 1/2] Some minor fixes/simplification --- benchmarks/algorithms.h | 88 ++++++++++++++++++++++++++++++++++------- benchmarks/floatutils.h | 6 ++- 2 files changed, 77 insertions(+), 17 deletions(-) diff --git a/benchmarks/algorithms.h b/benchmarks/algorithms.h index 2bcd87a..3480c6f 100644 --- a/benchmarks/algorithms.h +++ b/benchmarks/algorithms.h @@ -44,6 +44,7 @@ #define YY_DOUBLE_SUPPORTED 0 #endif + template struct BenchArgs { using Type = T; @@ -57,14 +58,7 @@ struct BenchArgs { bool used{}; size_t testRepeat{100}; - static void initFixedSize(size_t size) { - fixedSize = size; - snprintf(formatStr, sizeof(formatStr), "%%.%zug", fixedSize); - formatStrStr = fmt::format("{{:.{}g}}", fixedSize); - } static inline size_t fixedSize; - static inline char formatStr[10]; - static inline std::string formatStrStr; }; namespace BenchmarkShortest { @@ -321,21 +315,85 @@ int abseil(T d, std::span& buffer) { // absl::StrAppend(&s, d); // std::copy(s.begin(), s.end(), buffer.begin()); // return size(s); - return absl::SNPrintF(buffer.data(), buffer.size(), - BenchArgs::formatStr, d); + // + // The switch should be very cheap if fixedSize is very predictable. + // Essentially just a predictable jump. + switch (BenchArgs::fixedSize) { + case 0: return absl::SNPrintF(buffer.data(), buffer.size(), "%.0g", d); + case 1: return absl::SNPrintF(buffer.data(), buffer.size(), "%.1g", d); + case 2: return absl::SNPrintF(buffer.data(), buffer.size(), "%.2g", d); + case 3: return absl::SNPrintF(buffer.data(), buffer.size(), "%.3g", d); + case 4: return absl::SNPrintF(buffer.data(), buffer.size(), "%.4g", d); + case 5: return absl::SNPrintF(buffer.data(), buffer.size(), "%.5g", d); + case 6: return absl::SNPrintF(buffer.data(), buffer.size(), "%.6g", d); + case 7: return absl::SNPrintF(buffer.data(), buffer.size(), "%.7g", d); + case 8: return absl::SNPrintF(buffer.data(), buffer.size(), "%.8g", d); + case 9: return absl::SNPrintF(buffer.data(), buffer.size(), "%.9g", d); + case 10: return absl::SNPrintF(buffer.data(), buffer.size(), "%.10g", d); + case 11: return absl::SNPrintF(buffer.data(), buffer.size(), "%.11g", d); + case 12: return absl::SNPrintF(buffer.data(), buffer.size(), "%.12g", d); + case 13: return absl::SNPrintF(buffer.data(), buffer.size(), "%.13g", d); + case 14: return absl::SNPrintF(buffer.data(), buffer.size(), "%.14g", d); + case 15: return absl::SNPrintF(buffer.data(), buffer.size(), "%.15g", d); + case 16: return absl::SNPrintF(buffer.data(), buffer.size(), "%.16g", d); + case 17: return absl::SNPrintF(buffer.data(), buffer.size(), "%.17g", d); + default: + return absl::SNPrintF(buffer.data(), buffer.size(), "%.17g", d); + } } template int snprintf(T d, std::span& buffer) { - return std::snprintf(buffer.data(), buffer.size(), - BenchArgs::formatStr, d); + // The switch should be very cheap if fixedSize is very predictable. + // Essentially just a predictable jump. + switch (BenchArgs::fixedSize) { + case 0: return std::snprintf(buffer.data(), buffer.size(), "%.0g", d); + case 1: return std::snprintf(buffer.data(), buffer.size(), "%.1g", d); + case 2: return std::snprintf(buffer.data(), buffer.size(), "%.2g", d); + case 3: return std::snprintf(buffer.data(), buffer.size(), "%.3g", d); + case 4: return std::snprintf(buffer.data(), buffer.size(), "%.4g", d); + case 5: return std::snprintf(buffer.data(), buffer.size(), "%.5g", d); + case 6: return std::snprintf(buffer.data(), buffer.size(), "%.6g", d); + case 7: return std::snprintf(buffer.data(), buffer.size(), "%.7g", d); + case 8: return std::snprintf(buffer.data(), buffer.size(), "%.8g", d); + case 9: return std::snprintf(buffer.data(), buffer.size(), "%.9g", d); + case 10: return std::snprintf(buffer.data(), buffer.size(), "%.10g", d); + case 11: return std::snprintf(buffer.data(), buffer.size(), "%.11g", d); + case 12: return std::snprintf(buffer.data(), buffer.size(), "%.12g", d); + case 13: return std::snprintf(buffer.data(), buffer.size(), "%.13g", d); + case 14: return std::snprintf(buffer.data(), buffer.size(), "%.14g", d); + case 15: return std::snprintf(buffer.data(), buffer.size(), "%.15g", d); + case 16: return std::snprintf(buffer.data(), buffer.size(), "%.16g", d); + case 17: return std::snprintf(buffer.data(), buffer.size(), "%.17g", d); + default: + return std::snprintf(buffer.data(), buffer.size(), "%.17g", d); + } } template int fmt_format(T d, std::span& buffer) { - const auto it = fmt::format_to(buffer.begin(), - fmt::runtime(BenchArgs::formatStrStr), d); - return std::distance(buffer.begin(), it); + switch(BenchArgs::fixedSize) { + case 0: return fmt::format_to(buffer.data(), "{}", d) - buffer.data(); + case 1: return fmt::format_to(buffer.data(), "{:.1g}", d) - buffer.data(); + case 2: return fmt::format_to(buffer.data(), "{:.2g}", d) - buffer.data(); + case 3: return fmt::format_to(buffer.data(), "{:.3g}", d) - buffer.data(); + case 4: return fmt::format_to(buffer.data(), "{:.4g}", d) - buffer.data(); + case 5: return fmt::format_to(buffer.data(), "{:.5g}", d) - buffer.data(); + case 6: return fmt::format_to(buffer.data(), "{:.6g}", d) - buffer.data(); + case 7: return fmt::format_to(buffer.data(), "{:.7g}", d) - buffer.data(); + case 8: return fmt::format_to(buffer.data(), "{:.8g}", d) - buffer.data(); + case 9: return fmt::format_to(buffer.data(), "{:.9g}", d) - buffer.data(); + case 10: return fmt::format_to(buffer.data(), "{:.10g}", d) - buffer.data(); + case 11: return fmt::format_to(buffer.data(), "{:.11g}", d) - buffer.data(); + case 12: return fmt::format_to(buffer.data(), "{:.12g}", d) - buffer.data(); + case 13: return fmt::format_to(buffer.data(), "{:.13g}", d) - buffer.data(); + case 14: return fmt::format_to(buffer.data(), "{:.14g}", d) - buffer.data(); + case 15: return fmt::format_to(buffer.data(), "{:.15g}", d) - buffer.data(); + case 16: return fmt::format_to(buffer.data(), "{:.16g}", d) - buffer.data(); + case 17: return fmt::format_to(buffer.data(), "{:.17g}", d) - buffer.data(); + default: + return fmt::format_to(buffer.data(), "{:.17g}", d) - buffer.data(); + } } template @@ -407,7 +465,7 @@ std::vector> initArgs(bool use_errol = false, size_t repeat = 0, si // grisu2 does not round-trip correctly } else { // fixed-length representation fmt::println("# testing fixed-size output to {} digits", fixed_size); - BenchArgs::initFixedSize(fixed_size); + BenchArgs::fixedSize = fixed_size; namespace f = BenchmarkFixedSize; args.emplace_back("dragon4" , wrap(f::dragon4) , true , 10); diff --git a/benchmarks/floatutils.h b/benchmarks/floatutils.h index a279594..9ae6510 100644 --- a/benchmarks/floatutils.h +++ b/benchmarks/floatutils.h @@ -5,6 +5,7 @@ #include #include #include +#include template concept arithmetic_float @@ -47,8 +48,9 @@ std::optional parse_float(std::string_view sv) { T result; const char* begin = sv.data(); const char* end = sv.data() + sv.size(); - - auto [ptr, ec] = std::from_chars(begin, end, result); + // Using fastfloat for parsing and not std::from_chars, since + // fastfloat is always available and supports both float and double. + auto [ptr, ec] = fast_float::from_chars(begin, end, result); // Check if parsing succeeded and consumed the entire string if (ec == std::errc{} && ptr == end) { From af4909c8bd7b329f69be020984b83e91123aa507 Mon Sep 17 00:00:00 2001 From: Daniel Lemire Date: Tue, 3 Jun 2025 00:47:30 -0400 Subject: [PATCH 2/2] speeding up builds --- dependencies/CMakeLists.txt | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index e318d25..3dc7f81 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -14,8 +14,8 @@ if (NOT CYGWIN) set(ABSL_USE_GOOGLETEST_HEAD OFF CACHE INTERNAL "") FetchContent_Declare(abseil - GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git - GIT_TAG "d9e4955") # Abseil LTS branch, January 2025, Patch 1 + URL https://github.com/abseil/abseil-cpp/archive/d9e4955.zip + URL_HASH SHA256=b8292600560c7562cd4e96ba397ee205c0450af352061d9303d1e20d15ef8b6b) FetchContent_GetProperties(abseil) if(NOT abseil_POPULATED) set(BUILD_TESTING OFF) @@ -30,9 +30,8 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) FetchContent_Declare( doubleconversion - GIT_REPOSITORY https://github.com/google/double-conversion.git - GIT_TAG 101e1ba89dc41ceb75090831da97c43a76cd2906 - # GIT_SHALLOW TRUE + URL https://github.com/google/double-conversion/archive/101e1ba89dc41ceb75090831da97c43a76cd2906.zip + URL_HASH SHA256=be99800f12675849023235c77058c3f272cb0222891c9ac17101c2fbeed00c24 ) FetchContent_MakeAvailable(doubleconversion) @@ -60,7 +59,9 @@ FetchContent_Declare( GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(ryu) + set(ryu_SOURCE_DIR ${ryu_SOURCE_DIR} PARENT_SCOPE) +message(STATUS "ryu source directory: ${ryu_SOURCE_DIR}") FetchContent_Declare( grisu-exact @@ -70,6 +71,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(grisu-exact) set(grisu-exact_SOURCE_DIR ${grisu-exact_SOURCE_DIR} PARENT_SCOPE) +message(STATUS "grisu source directory: ${grisu-exact_SOURCE_DIR}") FetchContent_Declare( dragonbox @@ -78,6 +80,7 @@ FetchContent_Declare( GIT_SHALLOW TRUE ) FetchContent_MakeAvailable(dragonbox) +message(STATUS "dragonbox source directory: ${dragonbox_SOURCE_DIR}") FetchContent_Declare( dragon4 @@ -90,12 +93,15 @@ if(NOT dragon4) FetchContent_Populate(dragon4) # Downloads code to ${dragon4_SOURCE_DIR} endif() set(dragon4_SOURCE_DIR ${dragon4_SOURCE_DIR} PARENT_SCOPE) +message(STATUS "dragon4 source directory: ${dragon4_SOURCE_DIR}") + add_library(dragon4_lib STATIC ${dragon4_SOURCE_DIR}/Dragon4.cpp ${dragon4_SOURCE_DIR}/DragonMath.cpp ${dragon4_SOURCE_DIR}/PrintFloat.cpp ) if(MATH_LIBRARY) + message(STATUS "Using math library") target_link_libraries(dragon4_lib PUBLIC ${MATH_LIBRARY}) endif() target_include_directories(dragon4_lib PUBLIC @@ -104,17 +110,19 @@ target_include_directories(dragon4_lib PUBLIC # Swift code may not build under Windows without extra care. if(NOT WIN32) + message(STATUS "Grabbing Swift code") FetchContent_Declare( swift - GIT_REPOSITORY https://github.com/swiftlang/swift.git - GIT_TAG 6a862d2eb7128ff1f317b07e8ad1a6da939775f3 - GIT_SHALLOW TRUE + URL https://github.com/swiftlang/swift/archive/6a862d2eb7128ff1f317b07e8ad1a6da939775f3.tar.gz + URL_HASH SHA256=281673f70a1695ce26f5463ac43842ffdb546d6d888338f488f71015564e51a0 ) FetchContent_GetProperties(swift) if(NOT swift) FetchContent_Populate(swift) # Downloads code to ${dragon4_SOURCE_DIR} endif() set(swift_SOURCE_DIR ${swift_SOURCE_DIR} PARENT_SCOPE) + message(STATUS "Swift directory: ${swift_SOURCE_DIR}") + add_library(swift_lib STATIC ${swift_SOURCE_DIR}/stdlib/public/runtime/SwiftDtoa.cpp ) @@ -128,15 +136,16 @@ endif(NOT WIN32) FetchContent_Declare( drachennest # for schubfach - GIT_REPOSITORY https://github.com/abolz/Drachennest.git - GIT_TAG e6714a39ad331b4489d0b6aaf3968635bff4eb5e - GIT_SHALLOW TRUE + URL https://github.com/abolz/Drachennest/archive/e6714a39ad331b4489d0b6aaf3968635bff4eb5e.zip + URL_HASH SHA256=6a5526d17b47c8e8c264133d717d99693f01783aa4a50e144d7a0e28363647bb ) FetchContent_GetProperties(drachennest) if(NOT drachennest_POPULATED) FetchContent_Populate(drachennest) # Downloads code to ${drachennest_SOURCE_DIR} endif() set(drachennest_SOURCE_DIR/src ${drachennest_SOURCE_DIR}/src PARENT_SCOPE) +message(STATUS "drachennest directory: ${drachennest_SOURCE_DIR}") + add_library(dragon_schubfach_lib STATIC ${drachennest_SOURCE_DIR}/src/dragon4.cc ${drachennest_SOURCE_DIR}/src/schubfach_32.cc