diff --git a/libcxx/benchmarks/CMakeLists.txt b/libcxx/benchmarks/CMakeLists.txt --- a/libcxx/benchmarks/CMakeLists.txt +++ b/libcxx/benchmarks/CMakeLists.txt @@ -202,6 +202,7 @@ format.bench.cpp formatted_size.bench.cpp formatter_float.bench.cpp + formatter_int.bench.cpp std_format_spec_string_unicode.bench.cpp) endif() diff --git a/libcxx/benchmarks/formatter_int.bench.cpp b/libcxx/benchmarks/formatter_int.bench.cpp new file mode 100644 --- /dev/null +++ b/libcxx/benchmarks/formatter_int.bench.cpp @@ -0,0 +1,208 @@ +//===----------------------------------------------------------------------===// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "CartesianBenchmarks.h" + +// Tests the full range of the value. +template +static std::array +generate(std::uniform_int_distribution distribution = std::uniform_int_distribution{ + std::numeric_limits::min(), std::numeric_limits::max()}) { + std::mt19937 generator; + std::array result; + std::generate_n(result.begin(), result.size(), [&] { return distribution(generator); }); + return result; +} + +template +static void BM_Basic(benchmark::State& state) { + std::array data{generate()}; + std::array output; + + while (state.KeepRunningBatch(data.size())) + for (auto value : data) + benchmark::DoNotOptimize(std::format_to(output.begin(), "{}", value)); +} +BENCHMARK_TEMPLATE(BM_Basic, uint32_t); +BENCHMARK_TEMPLATE(BM_Basic, int32_t); +BENCHMARK_TEMPLATE(BM_Basic, uint64_t); +BENCHMARK_TEMPLATE(BM_Basic, int64_t); + +// Ideally the low values of a 128-bit value are all dispatched to a 64-bit routine. +template +static void BM_BasicLow(benchmark::State& state) { + using U = std::conditional_t, int64_t, uint64_t>; + std::array data{ + generate(std::uniform_int_distribution{std::numeric_limits::min(), std::numeric_limits::max()})}; + std::array output; + + while (state.KeepRunningBatch(data.size())) + for (auto value : data) + benchmark::DoNotOptimize(std::format_to(output.begin(), "{}", value)); +} +BENCHMARK_TEMPLATE(BM_BasicLow, __uint128_t); +BENCHMARK_TEMPLATE(BM_BasicLow, __int128_t); + +BENCHMARK_TEMPLATE(BM_Basic, __uint128_t); +BENCHMARK_TEMPLATE(BM_Basic, __int128_t); + +// *** Localization *** +enum class LocalizationE { False, True }; +struct AllLocalizations : EnumValuesAsTuple { + static constexpr const char* Names[] = {"LocFalse", "LocTrue"}; +}; + +template +struct Localization {}; + +template <> +struct Localization { + static constexpr const char* fmt = ""; +}; + +template <> +struct Localization { + static constexpr const char* fmt = "L"; +}; + +// *** Base *** +enum class BaseE { + Binary, + Octal, + Decimal, + Hex, + HexUpper, +}; +struct AllBases : EnumValuesAsTuple { + static constexpr const char* Names[] = {"BaseBin", "BaseOct", "BaseDec", "BaseHex", "BaseHexUpper"}; +}; + +template +struct Base {}; + +template <> +struct Base { + static constexpr const char* fmt = "b"; +}; + +template <> +struct Base { + static constexpr const char* fmt = "o"; +}; + +template <> +struct Base { + static constexpr const char* fmt = "d"; +}; + +template <> +struct Base { + static constexpr const char* fmt = "x"; +}; + +template <> +struct Base { + static constexpr const char* fmt = "X"; +}; + +// *** Types *** +enum class TypeE { Int64, Uint64 }; +struct AllTypes : EnumValuesAsTuple { + static constexpr const char* Names[] = {"Int64", "Uint64"}; +}; + +template +struct Type {}; + +template <> +struct Type { + using type = int64_t; + + static std::array make_data() { return generate(); } +}; + +template <> +struct Type { + using type = uint64_t; + + static std::array make_data() { return generate(); } +}; + +// *** Alignment *** +enum class AlignmentE { None, Left, Center, Right, ZeroPadding }; +struct AllAlignments : EnumValuesAsTuple { + static constexpr const char* Names[] = { + "AlignNone", "AlignmentLeft", "AlignmentCenter", "AlignmentRight", "ZeroPadding"}; +}; + +template +struct Alignment {}; + +template <> +struct Alignment { + static constexpr const char* fmt = ""; +}; + +template <> +struct Alignment { + static constexpr const char* fmt = "0<512"; +}; + +template <> +struct Alignment { + static constexpr const char* fmt = "0^512"; +}; + +template <> +struct Alignment { + static constexpr const char* fmt = "0>512"; +}; + +template <> +struct Alignment { + static constexpr const char* fmt = "0512"; +}; + +template +struct Integral { + void run(benchmark::State& state) const { + std::array data{Type::make_data()}; + std::array output; + + while (state.KeepRunningBatch(data.size())) + for (auto value : data) + benchmark::DoNotOptimize(std::format_to(output.begin(), std::string_view{fmt.data(), fmt.size()}, value)); + } + + std::string name() const { return "Integral" + L::name() + B::name() + A::name() + T::name(); } + + static constexpr std::string make_fmt() { + return std::string("{:") + Alignment::fmt + Localization::fmt + Base::fmt + "}"; + } + + static constexpr auto fmt = []() { + constexpr size_t s = make_fmt().size(); + std::array r; + std::ranges::copy(make_fmt(), r.begin()); + return r; + }(); +}; + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) + return 1; + + makeCartesianProductBenchmark(); + + benchmark::RunSpecifiedBenchmarks(); +}