diff --git a/include/fast_float/float_common.h b/include/fast_float/float_common.h index 9626ad8..c1fc86b 100644 --- a/include/fast_float/float_common.h +++ b/include/fast_float/float_common.h @@ -217,6 +217,12 @@ using parse_options = parse_options_t; #define fastfloat_really_inline inline __attribute__((always_inline)) #endif +#ifdef FASTFLOAT_VISUAL_STUDIO +#define FASTFLOAT_NOINLINE __declspec(noinline) +#else +#define FASTFLOAT_NOINLINE __attribute__((noinline, cold)) +#endif + // Branch-probability hint marking the rare slow-path branches as cold, so the // optimizer keeps the out-of-line slow-path re-parse off the hot path (and does // not duplicate the force-inlined hot scanner into the caller, which bloated diff --git a/include/fast_float/parse_number.h b/include/fast_float/parse_number.h index b0529e1..d05895f 100644 --- a/include/fast_float/parse_number.h +++ b/include/fast_float/parse_number.h @@ -139,7 +139,7 @@ fastfloat_really_inline bool rounds_to_nearest() noexcept { template struct from_chars_caller { template - FASTFLOAT_CONSTEXPR20 static from_chars_result_t + fastfloat_really_inline FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, T &value, parse_options_t options) noexcept { return from_chars_advanced(first, last, value, options); @@ -149,7 +149,7 @@ template struct from_chars_caller { #ifdef __STDCPP_FLOAT32_T__ template <> struct from_chars_caller { template - FASTFLOAT_CONSTEXPR20 static from_chars_result_t + fastfloat_really_inline FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float32_t &value, parse_options_t options) noexcept { // if std::float32_t is defined, and we are in C++23 mode; macro set for @@ -166,7 +166,7 @@ template <> struct from_chars_caller { #ifdef __STDCPP_FLOAT64_T__ template <> struct from_chars_caller { template - FASTFLOAT_CONSTEXPR20 static from_chars_result_t + fastfloat_really_inline FASTFLOAT_CONSTEXPR20 static from_chars_result_t call(UC const *first, UC const *last, std::float64_t &value, parse_options_t options) noexcept { // if std::float64_t is defined, and we are in C++23 mode; macro set for @@ -289,23 +289,32 @@ from_chars_advanced(parsed_number_string_t &pns, T &value) noexcept { return answer; } +template +FASTFLOAT_NOINLINE FASTFLOAT_CONSTEXPR20 parsed_number_string_t +parse_number_string_with_separator(UC const *first, UC const *last, + parse_options_t options, + bool store_spans) noexcept { + return parse_number_string(first, last, options, store_spans); +} + // Runtime -> compile-time dispatch over both boolean knobs of // parse_number_string. basic_json_fmt was already dispatched this way; the // digit separator is selected here too so that the separator-aware code paths -// stay confined to the (cold) has_separator==true instantiation. Callers that -// never set a separator -- the overwhelming majority -- run the +// stay confined to the (cold, out-of-line) has_separator==true instantiation. +// Callers that never set a separator -- the overwhelming majority -- run the // has_separator==false instantiation, which is byte-for-byte the original -// separator-free parser. +// separator-free parser; the separator check is a single predictable branch +// into cold code. template fastfloat_really_inline FASTFLOAT_CONSTEXPR20 parsed_number_string_t parse_number_string_options(UC const *first, UC const *last, parse_options_t options, bool bjf, bool store_spans) noexcept { - if (options.digit_separator != UC('\0')) { - return bjf ? parse_number_string(first, last, options, - store_spans) - : parse_number_string(first, last, options, - store_spans); + if fastfloat_unlikely (options.digit_separator != UC('\0')) { + return bjf ? parse_number_string_with_separator( + first, last, options, store_spans) + : parse_number_string_with_separator( + first, last, options, store_spans); } return bjf ? parse_number_string(first, last, options, store_spans)