Skip to content

Commit cc30cfd

Browse files
committed
perf: compile-time improvements for dimensions, units, and quantities formatting
1 parent 8e007d5 commit cc30cfd

File tree

3 files changed

+61
-27
lines changed

3 files changed

+61
-27
lines changed

src/core/include/mp-units/framework/dimension.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -384,12 +384,12 @@ class MP_UNITS_STD_FMT::formatter<D, Char> {
384384
auto specs = specs_;
385385
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width, specs.width_ref, ctx);
386386

387-
if (specs.width == 0)
388-
// Avoid extra copying if width is not specified
389-
return mp_units::dimension_symbol_to<Char>(ctx.out(), d, specs);
390-
std::basic_string<Char> unit_buffer;
391-
mp_units::dimension_symbol_to<Char>(std::back_inserter(unit_buffer), d, specs);
392-
return mp_units::detail::write_padded<Char>(ctx.out(), std::basic_string_view<Char>{unit_buffer}, specs.width,
387+
// Use a fixed-size stack buffer so that dimension_symbol_to is always instantiated
388+
// with Char* regardless of the FormatContext iterator type. This collapses all
389+
// call-site instantiations of dimension_symbol_impl to a single one (Char*).
390+
Char buf[128];
391+
const Char* const end = mp_units::dimension_symbol_to<Char>(buf, d, specs);
392+
return mp_units::detail::write_padded<Char>(ctx.out(), std::basic_string_view<Char>{buf, end}, specs.width,
393393
specs.align, specs.fill);
394394
}
395395
};

src/core/include/mp-units/framework/quantity.h

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,24 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
908908
}
909909
};
910910

911+
// Type-erased handler passed to parse_quantity_specs at runtime.
912+
// Being a concrete (non-template) type it ensures parse_quantity_specs is instantiated
913+
// only once for the runtime path, regardless of how many OutputIt types are used.
914+
struct erased_handler {
915+
void* self;
916+
void (*on_number_fn)(void*);
917+
void (*on_maybe_space_fn)(void*);
918+
void (*on_unit_fn)(void*);
919+
void (*on_dimension_fn)(void*);
920+
void (*on_text_fn)(void*, const Char*, const Char*);
921+
922+
void on_number() { on_number_fn(self); }
923+
void on_maybe_space() { on_maybe_space_fn(self); }
924+
void on_unit() { on_unit_fn(self); }
925+
void on_dimension() { on_dimension_fn(self); }
926+
void on_text(const Char* begin, const Char* end) { on_text_fn(self, begin, end); }
927+
};
928+
911929
template<typename OutputIt>
912930
struct quantity_formatter {
913931
const formatter& f;
@@ -933,10 +951,21 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
933951
out = MP_UNITS_STD_FMT::vformat_to(out, locale, f.dimension_format_str_,
934952
MP_UNITS_STD_FMT::make_format_args(q.dimension));
935953
}
936-
template<std::forward_iterator It>
937-
void on_text(It begin, It end) const
954+
void on_text(const Char* begin, const Char* end) { mp_units::detail::copy(begin, end, out); }
955+
956+
// Static trampolines for erased_handler — tiny, do not drag in parse_quantity_specs.
957+
static void on_number_erased(void* p) { static_cast<quantity_formatter*>(p)->on_number(); }
958+
static void on_maybe_space_erased(void* p) { static_cast<quantity_formatter*>(p)->on_maybe_space(); }
959+
static void on_unit_erased(void* p) { static_cast<quantity_formatter*>(p)->on_unit(); }
960+
static void on_dimension_erased(void* p) { static_cast<quantity_formatter*>(p)->on_dimension(); }
961+
static void on_text_erased(void* p, const Char* b, const Char* e)
962+
{
963+
static_cast<quantity_formatter*>(p)->on_text(b, e);
964+
}
965+
966+
erased_handler erase()
938967
{
939-
mp_units::detail::copy(begin, end, out);
968+
return {this, on_number_erased, on_maybe_space_erased, on_unit_erased, on_dimension_erased, on_text_erased};
940969
}
941970
};
942971
template<typename OutputIt, typename... Args>
@@ -1042,20 +1071,23 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
10421071
return begin;
10431072
}
10441073

1045-
template<typename OutputIt, typename FormatContext>
1046-
OutputIt format_quantity(OutputIt out, const quantity_t& q, FormatContext& ctx) const
1074+
// Writes the quantity content to `out`. Used for the width-buffering path.
1075+
template<typename OutputIt>
1076+
OutputIt format_to(OutputIt out, const quantity_t& q, const std::locale& locale) const
10471077
{
1048-
const std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale());
10491078
if (modifiers_format_str_.empty()) {
1050-
// default
1079+
// default layout: rep [space] unit
10511080
out = MP_UNITS_STD_FMT::vformat_to(out, locale, rep_format_str_,
10521081
MP_UNITS_STD_FMT::make_format_args(q.numerical_value_ref_in(q.unit)));
10531082
if constexpr (mp_units::space_before_unit_symbol<unit>) *out++ = ' ';
10541083
return MP_UNITS_STD_FMT::vformat_to(out, locale, unit_format_str_, MP_UNITS_STD_FMT::make_format_args(q.unit));
10551084
}
10561085
// user provided format
10571086
quantity_formatter f{*this, out, q, locale};
1058-
parse_quantity_specs(modifiers_format_str_.data(), modifiers_format_str_.data() + modifiers_format_str_.size(), f);
1087+
// Use erased_handler so parse_quantity_specs is instantiated only once (with erased_handler),
1088+
// not once per OutputIt type.
1089+
auto eh = f.erase();
1090+
parse_quantity_specs(modifiers_format_str_.data(), modifiers_format_str_.data() + modifiers_format_str_.size(), eh);
10591091
return f.out;
10601092
}
10611093

@@ -1081,7 +1113,9 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
10811113
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width, specs.width_ref, ctx);
10821114

10831115
if (specs.width == 0 && modifiers_format_str_.empty()) {
1084-
// Common fast path: call pre-parsed sub-formatters directly — no vformat_to, no allocation
1116+
// Fast path: no modifiers and no width — call pre-parsed sub-formatters directly.
1117+
// No vformat_to, no locale extraction, no allocation. Works correctly even when
1118+
// N[...]/U[...]/D[...] sub-specs were given: the spec is already encoded in the formatters.
10851119
ctx.advance_to(rep_formatter_.format(q.numerical_value_ref_in(q.unit), ctx));
10861120
if constexpr (mp_units::space_before_unit_symbol<unit>) {
10871121
auto it = ctx.out();
@@ -1090,13 +1124,13 @@ class MP_UNITS_STD_FMT::formatter<mp_units::quantity<Reference, Rep>, Char> {
10901124
}
10911125
return unit_formatter_.format(q.unit, ctx);
10921126
}
1093-
if (specs.width == 0) {
1094-
// Custom modifiers, no width
1095-
format_quantity(ctx.out(), q, ctx);
1096-
return ctx.out();
1097-
}
1127+
1128+
// Slow path: modifiers or width — must buffer or use vformat_to.
1129+
const std::locale locale = MP_UNITS_FMT_LOCALE(ctx.locale());
1130+
if (specs.width == 0) return format_to(ctx.out(), q, locale);
1131+
10981132
std::basic_string<Char> quantity_buffer;
1099-
format_quantity(std::back_inserter(quantity_buffer), q, ctx);
1133+
format_to(std::back_inserter(quantity_buffer), q, locale);
11001134
return mp_units::detail::write_padded<Char>(ctx.out(), std::basic_string_view<Char>{quantity_buffer}, specs.width,
11011135
specs.align, specs.fill);
11021136
}

src/core/include/mp-units/framework/unit.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,12 +1040,12 @@ class MP_UNITS_STD_FMT::formatter<U, Char> {
10401040
auto specs = specs_;
10411041
mp_units::detail::handle_dynamic_spec<mp_units::detail::width_checker>(specs.width, specs.width_ref, ctx);
10421042

1043-
if (specs.width == 0)
1044-
// Avoid extra copying if width is not specified
1045-
return mp_units::unit_symbol_to<Char>(ctx.out(), u, specs);
1046-
std::basic_string<Char> unit_buffer;
1047-
mp_units::unit_symbol_to<Char>(std::back_inserter(unit_buffer), u, specs);
1048-
return mp_units::detail::write_padded<Char>(ctx.out(), std::basic_string_view<Char>{unit_buffer}, specs.width,
1043+
// Use a fixed-size stack buffer so that unit_symbol_to is always instantiated
1044+
// with Char* regardless of the FormatContext iterator type. This collapses all
1045+
// call-site instantiations of unit_symbol_impl to a single one (Char*).
1046+
Char buf[128];
1047+
const Char* const end = mp_units::unit_symbol_to<Char>(buf, u, specs);
1048+
return mp_units::detail::write_padded<Char>(ctx.out(), std::basic_string_view<Char>{buf, end}, specs.width,
10491049
specs.align, specs.fill);
10501050
}
10511051
};

0 commit comments

Comments
 (0)