Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile-time named arguments #2243

Merged
merged 4 commits into from Apr 25, 2021

Conversation

alexezeder
Copy link
Contributor

@alexezeder alexezeder commented Apr 18, 2021

This PR makes the name of named arguments created via _a literal be available at compile-time. So named arguments can be checked at compile-time now exactly like non-named ones.
I haven't implemented these checks for FMT_STRING, but I did it for compile-time API. Thus these lines:

fmt::format(FMT_COMPILE("{0}"), 42);
fmt::format(FMT_COMPILE("{arg}"), "arg"_a = 42);

produce the same compiled format, so it's zero-overhead for arguments naming. Of course, fmt::arg(name, value)-based named arguments still have this overhead because they cannot provide their names at compile-time. Here are the results:

Benchmark Time CPU Iterations
FMTNonNamed/42 14.3 ns 14.3 ns 47750054
FMTRuntimeNamed/42 28.0 ns 28.0 ns 25015214
FMTCompileNonNamed/42 2.28 ns 2.28 ns 307524148
FMTCompileRuntimeNamed/42 3.05 ns 3.05 ns 229333563
FMTCompileCompileTimeNamed/42 2.23 ns 2.23 ns 316913616

Besides that performance improvement, a fallback to runtime API is no longer needed for named arguments with specs because type information is available at compile-time. Here are the results:

Benchmark Time CPU Iterations
FMTNonNamed/42 46.0 ns 46.0 ns 15185138
FMTRuntimeNamed/42 54.5 ns 54.5 ns 12507523
FMTCompileNonNamed/42 30.2 ns 30.2 ns 23434189
FMTCompileRuntimeNamed/42 54.3 ns 54.3 ns 12678573
FMTCompileCompileTimeNamed/42 29.8 ns 29.8 ns 23493430

Some points for this PR:

  • _a literal provides statically named argument (i.e. its name available at compile-time) in form of statically_named_arg type
  • existing fixed_string struct moved from compile.h to format.h because it's used in statically_named_arg, FMT_USE_NONTYPE_TEMPLATE_PARAMETERS macro definition also moved there
  • some is_named_arg-related fixes applied for correct statically_named_arg treating as named argument
  • field struct creation and field::format() changed to have the value type of named argument stored as a template T parameter (not named_arg<T>), same with spec_field
  • some part of compile_format_string() moved to separate function parse_replacement_field_then_tail() because it was duplicated 3 times, so maybe one more instantiation is not so bad 🤔
  • there is no other option but to use runtime_named_field for all named arguments that are not found
  • test for compiled format representation added

@alexezeder alexezeder force-pushed the feature/compile_time_named_args branch 2 times, most recently from 54fec9a to f720337 Compare April 19, 2021 12:02
@alexezeder
Copy link
Contributor Author

alexezeder commented Apr 20, 2021

There 2 more points for this PR. They are not implemented in this PR but can be added on request.

  1. Since every _a usages become templatized (with argument name as template argument), it probably affects compilation speed. I didn't benchmark how bad it does that, because IMHO such things as compile-time format string checks enabled by default would decrease compilation speed much more. Still, I can create a benchmark for this.

  2. there is no other option but to use runtime_named_field for all named arguments that are not found

    It's not completely true. Only arguments list with runtime and compile-time named arguments combined should invoke this behavior, while arguments list without runtime named arguments can fail compilation when such name is not found. But that would add more complexity in format string compilation code, and I'm not sure if it's worth it.

include/fmt/core.h Outdated Show resolved Hide resolved
Copy link
Contributor

@vitaut vitaut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR.

Still, I can create a benchmark for this.

That would be interesting to see if you have time.

But that would add more complexity in format string compilation code, and I'm not sure if it's worth it.

I think it's not worth it.

include/fmt/compile.h Outdated Show resolved Hide resolved
include/fmt/compile.h Show resolved Hide resolved
include/fmt/compile.h Outdated Show resolved Hide resolved
include/fmt/compile.h Outdated Show resolved Hide resolved
include/fmt/core.h Outdated Show resolved Hide resolved
struct is_named_arg<named_arg<Char, T>> : std::true_type {};

template <typename Char, typename T, typename... Tail,
FMT_ENABLE_IF(!is_named_arg<T>::value)>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is runtime named arg handling. Why do we need to care about compile-time ones here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because first, we need to provide a new entity for named arguments storing and adapt {fmt} for it. I mean before using this new entity in compile-time API.
This function took named_arg<Char, T>, which does not work for newly introduced statically_named_arg.

@alexezeder
Copy link
Contributor Author

Still, I can create a benchmark for this.

That would be interesting to see if you have time.

I always have time for benchmarks 😉
The final results are somewhat disappointing. Here they are:

Command Mean [s] Min [s] Max [s]
master -O0 1.443 ± 0.004 1.434 1.449
master -O3 2.723 ± 0.002 2.720 2.726
PR version -O0 2.119 ± 0.012 2.107 2.141
PR version -O3 3.226 ± 0.012 3.215 3.248

Details

I used hyperfine for proper time measurement.

Source file:

it's big, click to see
#include "fmt/format.h"

using namespace fmt;

std::string func(int fake) {
  switch (fake) {
  case 0:
    return format("{first}{second}{first}{third}", "first"_a = "abra",
                  "second"_a = "cad", "third"_a = 99);
  case 1:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "first_long_argument_name"_a = 41,
                  "second_long_argument_name"_a = 42,
                  "third_long_argument_name"_a = 43);
  case 2:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "first_first_first_very_very_very_long_long_long_argument_argument_argument_name_name_name"_a = 41,
                  "second_second_second_very_very_very_long_long_long_argument_argument_argument_name_name_name"_a = 42,
                  "third_third_third_very_very_very_long_long_long_argument_argument_argument_name_name_name"_a = 43);
  case 3:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "3first_long_argument_name"_a = 41,
                  "3second_long_argument_name"_a = 42,
                  "3third_long_argument_name"_a = 43);
  case 4:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "4first_long_argument_name"_a = 41,
                  "4second_long_argument_name"_a = 42,
                  "4third_long_argument_name"_a = 43);
  case 5:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "5first_long_argument_name"_a = 41,
                  "5second_long_argument_name"_a = 42,
                  "5third_long_argument_name"_a = 43);
  case 6:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "6first_long_argument_name"_a = 41,
                  "6second_long_argument_name"_a = 42,
                  "6third_long_argument_name"_a = 43);
  case 7:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "7first_long_argument_name"_a = 41,
                  "7second_long_argument_name"_a = 42,
                  "7third_long_argument_name"_a = 43);
  case 8:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "8first_long_argument_name"_a = 41,
                  "8second_long_argument_name"_a = 42,
                  "8third_long_argument_name"_a = 43);
  case 9:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "9first_long_argument_name"_a = 41,
                  "9second_long_argument_name"_a = 42,
                  "9third_long_argument_name"_a = 43);
  case 10:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "10first_long_argument_name"_a = 41,
                  "10second_long_argument_name"_a = 42,
                  "10third_long_argument_name"_a = 43);
  case 11:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "11first_long_argument_name"_a = 41,
                  "11second_long_argument_name"_a = 42,
                  "11third_long_argument_name"_a = 43);
  case 12:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "12first_long_argument_name"_a = 41,
                  "12second_long_argument_name"_a = 42,
                  "12third_long_argument_name"_a = 43);
  case 13:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "13first_long_argument_name"_a = 41,
                  "13second_long_argument_name"_a = 42,
                  "13third_long_argument_name"_a = 43);
  case 14:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "14first_long_argument_name"_a = 41,
                  "14second_long_argument_name"_a = 42,
                  "14third_long_argument_name"_a = 43);
  case 15:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "15first_long_argument_name"_a = 41,
                  "15second_long_argument_name"_a = 42,
                  "15third_long_argument_name"_a = 43);
  case 16:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "16first_long_argument_name"_a = 41,
                  "16second_long_argument_name"_a = 42,
                  "16third_long_argument_name"_a = 43);
  case 17:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "17first_long_argument_name"_a = 41,
                  "17second_long_argument_name"_a = 42,
                  "17third_long_argument_name"_a = 43);
  case 18:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "18first_long_argument_name"_a = 41,
                  "18second_long_argument_name"_a = 42,
                  "18third_long_argument_name"_a = 43);
  case 19:
    return format("{first}{second}{first}{third}{first}{second}{first}{third}",
                  "19first_long_argument_name"_a = 41,
                  "19second_long_argument_name"_a = 42,
                  "19third_long_argument_name"_a = 43);
  default:
    return {};
  }
}

std::wstring wfunc(int fake) {
  switch (fake) {
  case 0:
    return format(L"{first}{second}{first}{third}", L"first"_a = L"abra",
                  L"second"_a = L"cad", L"third"_a = 99);
  case 1:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"first_long_argument_name"_a = 41,
                  L"second_long_argument_name"_a = 42,
                  L"third_long_argument_name"_a = 43);
  case 2:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"first_first_first_very_very_very_long_long_long_argument_argument_argument_name_name_name"_a = 41,
                  L"second_second_second_very_very_very_long_long_long_argument_argument_argument_name_name_name"_a = 42,
                  L"third_third_third_very_very_very_long_long_long_argument_argument_argument_name_name_name"_a = 43);
  case 3:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"3first_long_argument_name"_a = 41,
                  L"3second_long_argument_name"_a = 42,
                  L"3third_long_argument_name"_a = 43);
  case 4:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"4first_long_argument_name"_a = 41,
                  L"4second_long_argument_name"_a = 42,
                  L"4third_long_argument_name"_a = 43);
  case 5:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"5first_long_argument_name"_a = 41,
                  L"5second_long_argument_name"_a = 42,
                  L"5third_long_argument_name"_a = 43);
  case 6:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"6first_long_argument_name"_a = 41,
                  L"6second_long_argument_name"_a = 42,
                  L"6third_long_argument_name"_a = 43);
  case 7:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"7first_long_argument_name"_a = 41,
                  L"7second_long_argument_name"_a = 42,
                  L"7third_long_argument_name"_a = 43);
  case 8:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"8first_long_argument_name"_a = 41,
                  L"8second_long_argument_name"_a = 42,
                  L"8third_long_argument_name"_a = 43);
  case 9:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"9first_long_argument_name"_a = 41,
                  L"9second_long_argument_name"_a = 42,
                  L"9third_long_argument_name"_a = 43);
  case 10:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"10first_long_argument_name"_a = 41,
                  L"10second_long_argument_name"_a = 42,
                  L"10third_long_argument_name"_a = 43);
  case 11:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"11first_long_argument_name"_a = 41,
                  L"11second_long_argument_name"_a = 42,
                  L"11third_long_argument_name"_a = 43);
  case 12:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"12first_long_argument_name"_a = 41,
                  L"12second_long_argument_name"_a = 42,
                  L"12third_long_argument_name"_a = 43);
  case 13:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"13first_long_argument_name"_a = 41,
                  L"13second_long_argument_name"_a = 42,
                  L"13third_long_argument_name"_a = 43);
  case 14:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"14first_long_argument_name"_a = 41,
                  L"14second_long_argument_name"_a = 42,
                  L"14third_long_argument_name"_a = 43);
  case 15:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"15first_long_argument_name"_a = 41,
                  L"15second_long_argument_name"_a = 42,
                  L"15third_long_argument_name"_a = 43);
  case 16:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"16first_long_argument_name"_a = 41,
                  L"16second_long_argument_name"_a = 42,
                  L"16third_long_argument_name"_a = 43);
  case 17:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"17first_long_argument_name"_a = 41,
                  L"17second_long_argument_name"_a = 42,
                  L"17third_long_argument_name"_a = 43);
  case 18:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"18first_long_argument_name"_a = 41,
                  L"18second_long_argument_name"_a = 42,
                  L"18third_long_argument_name"_a = 43);
  case 19:
    return format(L"{first}{second}{first}{third}{first}{second}{first}{third}",
                  L"19first_long_argument_name"_a = 41,
                  L"19second_long_argument_name"_a = 42,
                  L"19third_long_argument_name"_a = 43);
  default:
    return {};
  }
}

Command:

mkdir build && cd build
g++-10 -I../include -std=c++20 -c source_file.cpp -o output_file -O{optimization level here}

@alexezeder alexezeder force-pushed the feature/compile_time_named_args branch from b66d262 to d4b727d Compare April 23, 2021 19:42
@alexezeder
Copy link
Contributor Author

alexezeder commented Apr 23, 2021

Here are more detailed benchmarks. Again, hyperfine was used for proper time measurement.

There are more benchmarks this time on GCC 10.2, they demonstrate different use cases. Each benchmark name contains:

  • quantity of fmt::format() invocations (with 3 named arguments):
    • one - only 1 invocation
    • several - 10
    • many - 50
    • too_many - 500
  • length of literals used for named arguments:
    • short
      "x"_a = 41,
      "arg"_a = 42,
      "a"_a = 43
    • medium
      "some_medium_name"_a = 41, 
      "another_medium_name"_a = 42, 
      "last_medium_name"_a = 43
    • long
      "this_argument_name_is_quite_loooooooong"_a = 41, 
      "very_very_very_very_very_very_long_argument_name"_a = 42, 
      "argument_argument_argument_argument_argument_argument"_a = 43
  • prefix w for benchmarks with wide chars (L literal).

For example, source file for several_short_w looks something like this:

#include "fmt/format.h"
using namespace fmt;
void func(char* output) {
  output = format_to(output, L"does not matter", L"0x"_a = 41, L"0arg"_a = 42, L"0a"_a = 43);
  output = format_to(output, L"does not matter", L"1x"_a = 41, L"1arg"_a = 42, L"1a"_a = 43);
  output = format_to(output, L"does not matter", L"2x"_a = 41, L"2arg"_a = 42, L"2a"_a = 43);
  output = format_to(output, L"does not matter", L"3x"_a = 41, L"3arg"_a = 42, L"3a"_a = 43);
  output = format_to(output, L"does not matter", L"4x"_a = 41, L"4arg"_a = 42, L"4a"_a = 43);
  output = format_to(output, L"does not matter", L"5x"_a = 41, L"5arg"_a = 42, L"5a"_a = 43);
  output = format_to(output, L"does not matter", L"6x"_a = 41, L"6arg"_a = 42, L"6a"_a = 43);
  output = format_to(output, L"does not matter", L"7x"_a = 41, L"7arg"_a = 42, L"7a"_a = 43);
  output = format_to(output, L"does not matter", L"8x"_a = 41, L"8arg"_a = 42, L"8a"_a = 43);
  output = format_to(output, L"does not matter", L"9x"_a = 41, L"9arg"_a = 42, L"9a"_a = 43);
}

As you can see, there are prefixes for all named arguments to eliminate potential optimizations, but I'm not sure that there would be any. And yes, these prefixes are syntactically wrong, but this function is never invoked. Also note that this is a runtime API, with static named arguments.

Name master O0 [s] PR O0 [s] diff master O3 [s] PR O3 [s] diff
one_short 0.668 0.675 0.685 0.677
several_short 0.666 0.848 1.3x slower 0.705 0.827 1.2x slower
many_short 0.696 1.608 2.3x slower 0.880 1.514 1.7x slower
one_medium 0.678 0.671 0.677 0.693
several_medium 0.676 0.850 1.3x slower 0.697 0.840 1.2x slower
many_medium 0.707 1.643 2.3x slower 0.891 1.564 1.8x slower
one_long 0.672 0.683 0.674 0.692
several_long 0.667 0.870 1.3x slower 0.698 0.850 1.2x slower
many_long 0.699 1.706 2.4x slower 0.885 1.606 1.8x slower
too_many_long 0.989 12.933 13.1x slower 3.849 12.029 3.1x slower
one_short_w 1.306 1.333 2.559 2.574
several_short_w 1.334 1.500 1.1x slower 2.759 2.700
many_short_w 1.323 2.258 1.7x slower 2.742 3.368 1.2x slower
one_medium_w 1.317 1.326 2.561 2.535
several_medium_w 1.309 1.510 1.2x slower 2.762 2.713
many_medium_w 1.330 2.303 1.7x slower 2.726 3.440 1.3x slower
one_long_w 1.312 1.331 2.546 2.544
several_long_w 1.291 1.521 1.2x slower 2.730 2.720
many_long_w 1.351 2.399 1.8x slower 2.747 3.452 1.3x slower
too_many_long_w 1.694 13.764 8.1x slower 4.764 14.660 3.1x slower
Raw data:

master:

Benchmark Mean [s] Min [s] Max [s] Relative
one_short -O0 668.8 ± 9.5 650.9 680.3 1.00
one_short -O3 685.5 ± 2.4 682.8 689.3 1.00
one_short_w -O0 1.306 ± 0.014 1.285 1.318 1.00
one_short_w -O3 2.559 ± 0.015 2.544 2.579 1.00
many_short -O0 696.9 ± 1.8 693.4 700.0 1.00
many_short -O3 880.2 ± 7.3 865.6 886.2 1.00
many_short_w -O0 1.323 ± 0.013 1.313 1.344 1.00
many_short_w -O3 2.742 ± 0.015 2.715 2.763 1.00
one_medium -O0 678.6 ± 3.0 676.2 686.4 1.00
one_medium -O3 677.7 ± 6.3 661.5 685.1 1.00
one_medium_w -O0 1.317 ± 0.004 1.312 1.325 1.00
one_medium_w -O3 2.561 ± 0.016 2.548 2.588 1.00
many_medium -O0 707.4 ± 1.5 705.6 710.3 1.00
many_medium -O3 891.4 ± 4.9 881.4 896.6 1.00
many_medium_w -O0 1.330 ± 0.012 1.315 1.356 1.00
many_medium_w -O3 2.726 ± 0.014 2.712 2.744 1.00
one_long -O0 672.0 ± 3.4 668.0 676.7 1.00
one_long -O3 674.2 ± 8.8 656.6 687.1 1.00
one_long_w -O0 1.312 ± 0.010 1.286 1.325 1.00
one_long_w -O3 2.546 ± 0.015 2.530 2.562 1.00
many_long -O0 699.6 ± 4.3 695.4 710.5 1.00
many_long -O3 885.7 ± 5.3 875.9 895.8 1.00
many_long_w -O0 1.351 ± 0.005 1.347 1.360 1.00
many_long_w -O3 2.747 ± 0.012 2.737 2.769 1.00
too_many_long -O0 989.3 ± 5.4 984.8 1002.1 1.00
too_many_long -O3 3.849 ± 0.013 3.838 3.878 1.00
too_many_long_w -O0 1.694 ± 0.014 1.664 1.711 1.00
too_many_long_w -O3 4.764 ± 0.014 4.749 4.787 1.00
several_short -O0 666.1 ± 6.5 652.7 672.2 1.00
several_short -O3 705.3 ± 9.2 689.1 714.1 1.00
several_short_w -O0 1.334 ± 0.005 1.328 1.346 1.00
several_short_w -O3 2.759 ± 0.012 2.748 2.777 1.00
several_medium -O0 676.9 ± 5.6 661.5 680.6 1.00
several_medium -O3 697.4 ± 4.8 693.3 708.5 1.00
several_medium_w -O0 1.309 ± 0.002 1.306 1.315 1.00
several_medium_w -O3 2.762 ± 0.015 2.747 2.782 1.00
several_long -O0 667.8 ± 6.7 649.9 674.2 1.00
several_long -O3 698.1 ± 6.8 693.4 715.2 1.00
several_long_w -O0 1.291 ± 0.013 1.278 1.318 1.00
several_long_w -O3 2.730 ± 0.018 2.713 2.758 1.00

PR:

Benchmark Mean [s] Min [s] Max [s] Relative
one_short -O0 675.7 ± 5.8 670.3 689.9 1.00
one_short -O3 677.7 ± 3.5 674.7 686.2 1.00
one_short_w -O0 1.333 ± 0.009 1.308 1.338 1.00
one_short_w -O3 2.574 ± 0.015 2.560 2.594 1.00
many_short -O0 1.608 ± 0.016 1.587 1.623 1.00
many_short -O3 1.514 ± 0.009 1.502 1.536 1.00
many_short_w -O0 2.258 ± 0.003 2.255 2.263 1.00
many_short_w -O3 3.368 ± 0.004 3.363 3.374 1.00
one_medium -O0 671.1 ± 7.0 653.9 682.3 1.00
one_medium -O3 693.0 ± 3.5 690.3 701.9 1.00
one_medium_w -O0 1.326 ± 0.014 1.307 1.337 1.00
one_medium_w -O3 2.535 ± 0.015 2.521 2.559 1.00
many_medium -O0 1.643 ± 0.017 1.617 1.658 1.00
many_medium -O3 1.564 ± 0.006 1.559 1.580 1.00
many_medium_w -O0 2.303 ± 0.009 2.293 2.326 1.00
many_medium_w -O3 3.440 ± 0.014 3.419 3.458 1.00
one_long -O0 683.2 ± 2.6 679.9 689.1 1.00
one_long -O3 692.5 ± 7.1 673.2 700.5 1.00
one_long_w -O0 1.331 ± 0.011 1.309 1.338 1.00
one_long_w -O3 2.544 ± 0.015 2.524 2.566 1.00
many_long -O0 1.706 ± 0.005 1.700 1.715 1.00
many_long -O3 1.606 ± 0.005 1.599 1.615 1.00
many_long_w -O0 2.399 ± 0.022 2.353 2.441 1.00
many_long_w -O3 3.452 ± 0.014 3.429 3.469 1.00
too_many_long -O0 12.933 ± 0.071 12.865 13.063 1.00
too_many_long -O3 12.029 ± 0.025 12.001 12.077 1.00
too_many_long_w -O0 13.764 ± 0.063 13.664 13.819 1.00
too_many_long_w -O3 14.660 ± 0.027 14.613 14.697 1.00
several_short -O0 848.9 ± 8.4 842.7 867.6 1.00
several_short -O3 827.9 ± 2.8 825.0 834.5 1.00
several_short_w -O0 1.500 ± 0.014 1.479 1.511 1.00
several_short_w -O3 2.700 ± 0.015 2.688 2.731 1.00
several_medium -O0 850.1 ± 8.9 840.4 864.3 1.00
several_medium -O3 840.0 ± 7.2 821.9 850.1 1.00
several_medium_w -O0 1.510 ± 0.013 1.492 1.522 1.00
several_medium_w -O3 2.713 ± 0.016 2.696 2.736 1.00
several_long -O0 870.2 ± 7.6 859.0 889.1 1.00
several_long -O3 850.7 ± 5.8 846.1 862.9 1.00
several_long_w -O0 1.521 ± 0.014 1.502 1.535 1.00
several_long_w -O3 2.720 ± 0.018 2.702 2.759 1.00
Python script for source files generation:
head = '''
#include "fmt/format.h"

using namespace fmt;

void func(char* output) {
'''

short_format_call = '''
  output = format_to(output, 
                     {literal_prefix}"does not matter", 
                     {literal_prefix}"{arg_prefix}x"_a = 41, 
                     {literal_prefix}"{arg_prefix}arg"_a = 42, 
                     {literal_prefix}"{arg_prefix}a"_a = 43);
'''

medium_format_call = '''
  output = format_to(output, 
                     {literal_prefix}"does not matter", 
                     {literal_prefix}"{arg_prefix}some_medium_name"_a = 41, 
                     {literal_prefix}"{arg_prefix}another_medium_name"_a = 42, 
                     {literal_prefix}"{arg_prefix}last_medium_name"_a = 43);
'''

long_format_call = '''
  output = format_to(output, 
                     {literal_prefix}"does not matter", 
                     {literal_prefix}"{arg_prefix}this_argument_name_is_quite_loooooooong"_a = 41, 
                     {literal_prefix}"{arg_prefix}very_very_very_very_very_very_long_argument_name"_a = 42, 
                     {literal_prefix}"{arg_prefix}argument_argument_argument_argument_argument_argument"_a = 43);
'''

tail = '''
}
'''


def compose(format_call: str, is_wide: bool, n: int):
    result: str = ''
    result += head
    if n == 1:
        result += format_call.format(literal_prefix=('L' if is_wide else ''), arg_prefix='')
    else:
        for x in range(0, n):
            result += format_call.format(literal_prefix=('L' if is_wide else ''), arg_prefix=x)
    result += tail
    return result


def main():
    with open('one_short.cpp', 'w') as f:
        print(compose(short_format_call, False, 1), file=f)
    with open('one_short_w.cpp', 'w') as f:
        print(compose(short_format_call, True, 1), file=f)
    with open('several_short.cpp', 'w') as f:
        print(compose(short_format_call, False, 10), file=f)
    with open('several_short_w.cpp', 'w') as f:
        print(compose(short_format_call, True, 10), file=f)
    with open('many_short.cpp', 'w') as f:
        print(compose(short_format_call, False, 50), file=f)
    with open('many_short_w.cpp', 'w') as f:
        print(compose(short_format_call, True, 50), file=f)

    with open('one_medium.cpp', 'w') as f:
        print(compose(medium_format_call, False, 1), file=f)
    with open('one_medium_w.cpp', 'w') as f:
        print(compose(medium_format_call, True, 1), file=f)
    with open('several_medium.cpp', 'w') as f:
        print(compose(medium_format_call, False, 10), file=f)
    with open('several_medium_w.cpp', 'w') as f:
        print(compose(medium_format_call, True, 10), file=f)
    with open('many_medium.cpp', 'w') as f:
        print(compose(medium_format_call, False, 50), file=f)
    with open('many_medium_w.cpp', 'w') as f:
        print(compose(medium_format_call, True, 50), file=f)

    with open('one_long.cpp', 'w') as f:
        print(compose(long_format_call, False, 1), file=f)
    with open('one_long_w.cpp', 'w') as f:
        print(compose(long_format_call, True, 1), file=f)
    with open('several_long.cpp', 'w') as f:
        print(compose(long_format_call, False, 10), file=f)
    with open('several_long_w.cpp', 'w') as f:
        print(compose(long_format_call, True, 10), file=f)
    with open('many_long.cpp', 'w') as f:
        print(compose(long_format_call, False, 50), file=f)
    with open('many_long_w.cpp', 'w') as f:
        print(compose(long_format_call, True, 50), file=f)

    with open('too_many_long.cpp', 'w') as f:
        print(compose(long_format_call, False, 500), file=f)
    with open('too_many_long_w.cpp', 'w') as f:
        print(compose(long_format_call, True, 500), file=f)


if __name__ == '__main__':
    main()

@alexezeder alexezeder force-pushed the feature/compile_time_named_args branch from d4b727d to 04603b5 Compare April 23, 2021 23:54
@alexezeder alexezeder requested a review from vitaut April 24, 2021 00:09
@vitaut vitaut merged commit 1678ed6 into fmtlib:master Apr 25, 2021
@vitaut
Copy link
Contributor

vitaut commented Apr 25, 2021

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants