From b8f53a08138b43bbe4400aec230777a0babd2635 Mon Sep 17 00:00:00 2001 From: Khalil Estell Date: Thu, 16 Jul 2020 14:50:12 -0700 Subject: [PATCH] Remove from int_writer Reduce code bloat by removing multiple instantiation of int_writer based on the parameter. Rationale: - The only functions that gains a speedup by int size would be int_writer::on_dec()'s call to count_digits which uses CLZ. Thus to still take advantage of this speedup, we store the size of the int so we can use a switch statement to call the correct count_digits. - All other implementations of count_digits require some sort of looping that terminates when the value hits zero regardless of what sized int it is. Caveats: - There is a performance hit when dealing with and passing around 64-bit/128-bit values compared to 32-bit values on 32-bit platforms, and with 64-bit values on 64-bit systems. But this should not reduce the performance that dramatically. - There is also a performance hit for on_dec() due to the addition of a switch case. But, due to it size, this should reduce to a jump table. Resolves #1778 --- include/fmt/format.h | 52 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/include/fmt/format.h b/include/fmt/format.h index fc46c9f0..989819f3 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -733,6 +733,12 @@ using uint32_or_64_or_128_t = conditional_t< std::numeric_limits::digits <= 32, uint32_t, conditional_t::digits <= 64, uint64_t, uint128_t>>; +// Selects the between uint64_t or uint128_t based on the how uint128_t is +// defined. If macro FMT_USE_INT128 defined as 0, then its size will be 1 byte, +// meaning the largest sized int that can be used is uint64_t. +using uint_largest_t = + conditional_t; + // Static data is placed in this class template for the header-only config. template struct FMT_EXTERN_TEMPLATE_API basic_data { static const uint64_t powers_of_10_64[]; @@ -1458,11 +1464,21 @@ OutputIt write(OutputIt out, basic_string_view s, } // The handle_int_type_spec handler that writes an integer. -template struct int_writer { +template struct int_writer { + enum class int_bytes { + byte1 = sizeof(uint8_t), + byte2 = sizeof(uint16_t), + byte4 = sizeof(uint32_t), + byte8 = sizeof(uint64_t), + byte16 = 16, // Must be directly set because uint128_t can be 16 bytes if + // FMT_USE_INT128 == 1 and 1 byte if FMT_USE_INT128 == 0. + }; + OutputIt out; locale_ref locale; const basic_format_specs& specs; - UInt abs_value; + uint_largest_t abs_value; + int_bytes value_bytes; char prefix[4]; unsigned prefix_size; @@ -1477,9 +1493,11 @@ template struct int_writer { : out(output), locale(loc), specs(s), - abs_value(static_cast(value)), + abs_value(static_cast(value)), + value_bytes(int_bytes::byte8), prefix_size(0) { - static_assert(std::is_same, UInt>::value, ""); + value_bytes = static_cast(sizeof(value)); + if (is_negative(value)) { prefix[0] = '-'; ++prefix_size; @@ -1491,7 +1509,28 @@ template struct int_writer { } void on_dec() { - auto num_digits = count_digits(abs_value); + int num_digits = 0; + + switch (value_bytes) { + case int_bytes::byte1: + case int_bytes::byte2: + case int_bytes::byte4: + num_digits = count_digits(static_cast(abs_value)); + break; + case int_bytes::byte8: + num_digits = count_digits(static_cast(abs_value)); + break; +#if !FMT_USE_INT128 + case int_bytes::byte16: + num_digits = count_digits(static_cast(abs_value)); + break; +#else + case int_bytes::byte16: + num_digits = count_digits(abs_value); + break; +#endif + } + out = write_int( out, num_digits, get_prefix(), specs, [this, num_digits](iterator it) { return format_decimal(it, abs_value, num_digits).end; @@ -1831,8 +1870,7 @@ class arg_formatter_base { detail::reserve(std::declval(), 0))>; template void write_int(T value, const format_specs& spec) { - using uint_type = uint32_or_64_or_128_t; - int_writer w(out_, locale_, value, spec); + int_writer w(out_, locale_, value, spec); handle_int_type_spec(spec.type, w); out_ = w.out; }