| //===----------------------------------------------------------------------===// | |
| // 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 <format> | |
| #include <array> | |
| #include <limits> | |
| #include <random> | |
| #include <string> | |
| #include "CartesianBenchmarks.h" | |
| #include "benchmark/benchmark.h" | |
| // *** Localization *** | |
| enum class LocalizationE { False, True }; | |
| struct AllLocalizations : EnumValuesAsTuple<AllLocalizations, LocalizationE, 2> { | |
| static constexpr const char* Names[] = {"LocFalse", "LocTrue"}; | |
| }; | |
| template <LocalizationE E> | |
| struct Localization {}; | |
| template <> | |
| struct Localization<LocalizationE::False> { | |
| static constexpr const char* fmt = ""; | |
| }; | |
| template <> | |
| struct Localization<LocalizationE::True> { | |
| static constexpr const char* fmt = "L"; | |
| }; | |
| // *** Types *** | |
| enum class TypeE { Float, Double, LongDouble }; | |
| // TODO FMT Set to 3 after to_chars has long double suport. | |
| struct AllTypes : EnumValuesAsTuple<AllTypes, TypeE, 2> { | |
| static constexpr const char* Names[] = {"Float", "Double", "LongDouble"}; | |
| }; | |
| template <TypeE E> | |
| struct Type {}; | |
| template <> | |
| struct Type<TypeE::Float> { | |
| using type = float; | |
| }; | |
| template <> | |
| struct Type<TypeE::Double> { | |
| using type = double; | |
| }; | |
| template <> | |
| struct Type<TypeE::LongDouble> { | |
| using type = long double; | |
| }; | |
| // *** Values *** | |
| enum class ValueE { Inf, Random }; | |
| struct AllValues : EnumValuesAsTuple<AllValues, ValueE, 2> { | |
| static constexpr const char* Names[] = {"Inf", "Random"}; | |
| }; | |
| template <ValueE E> | |
| struct Value {}; | |
| template <> | |
| struct Value<ValueE::Inf> { | |
| template <class F> | |
| static std::array<F, 1000> make_data() { | |
| std::array<F, 1000> result; | |
| std::fill(result.begin(), result.end(), -std::numeric_limits<F>::infinity()); | |
| return result; | |
| } | |
| }; | |
| template <> | |
| struct Value<ValueE::Random> { | |
| template <class F> | |
| static std::array<F, 1000> make_data() { | |
| std::random_device seed; | |
| std::mt19937 generator(seed()); | |
| std::uniform_int_distribution<std::conditional_t<sizeof(F) == sizeof(uint32_t), uint32_t, uint64_t>> distribution; | |
| std::array<F, 1000> result; | |
| std::generate(result.begin(), result.end(), [&] { | |
| while (true) { | |
| auto result = std::bit_cast<F>(distribution(generator)); | |
| if (std::isfinite(result)) | |
| return result; | |
| } | |
| }); | |
| return result; | |
| } | |
| }; | |
| // *** Display Type *** | |
| enum class DisplayTypeE { | |
| Default, | |
| Hex, | |
| Scientific, | |
| Fixed, | |
| General, | |
| }; | |
| struct AllDisplayTypes : EnumValuesAsTuple<AllDisplayTypes, DisplayTypeE, 5> { | |
| static constexpr const char* Names[] = {"DisplayDefault", "DisplayHex", "DisplayScientific", "DisplayFixed", | |
| "DisplayGeneral"}; | |
| }; | |
| template <DisplayTypeE E> | |
| struct DisplayType {}; | |
| template <> | |
| struct DisplayType<DisplayTypeE::Default> { | |
| static constexpr const char* fmt = ""; | |
| }; | |
| template <> | |
| struct DisplayType<DisplayTypeE::Hex> { | |
| static constexpr const char* fmt = "a"; | |
| }; | |
| template <> | |
| struct DisplayType<DisplayTypeE::Scientific> { | |
| static constexpr const char* fmt = "e"; | |
| }; | |
| template <> | |
| struct DisplayType<DisplayTypeE::Fixed> { | |
| static constexpr const char* fmt = "f"; | |
| }; | |
| template <> | |
| struct DisplayType<DisplayTypeE::General> { | |
| static constexpr const char* fmt = "g"; | |
| }; | |
| // *** Alignment *** | |
| enum class AlignmentE { None, Left, Center, Right, ZeroPadding }; | |
| struct AllAlignments : EnumValuesAsTuple<AllAlignments, AlignmentE, 5> { | |
| static constexpr const char* Names[] = {"AlignNone", "AlignmentLeft", "AlignmentCenter", "AlignmentRight", | |
| "ZeroPadding"}; | |
| }; | |
| template <AlignmentE E> | |
| struct Alignment {}; | |
| template <> | |
| struct Alignment<AlignmentE::None> { | |
| static constexpr const char* fmt = ""; | |
| }; | |
| template <> | |
| struct Alignment<AlignmentE::Left> { | |
| // Width > PrecisionE::Huge | |
| static constexpr const char* fmt = "0<17500"; | |
| }; | |
| template <> | |
| struct Alignment<AlignmentE::Center> { | |
| // Width > PrecisionE::Huge | |
| static constexpr const char* fmt = "0^17500"; | |
| }; | |
| template <> | |
| struct Alignment<AlignmentE::Right> { | |
| // Width > PrecisionE::Huge | |
| static constexpr const char* fmt = "0>17500"; | |
| }; | |
| template <> | |
| struct Alignment<AlignmentE::ZeroPadding> { | |
| // Width > PrecisionE::Huge | |
| static constexpr const char* fmt = "017500"; | |
| }; | |
| enum class PrecisionE { None, Zero, Small, Huge }; | |
| struct AllPrecisions : EnumValuesAsTuple<AllPrecisions, PrecisionE, 4> { | |
| static constexpr const char* Names[] = {"PrecNone", "PrecZero", "PrecSmall", "PrecHuge"}; | |
| }; | |
| template <PrecisionE E> | |
| struct Precision {}; | |
| template <> | |
| struct Precision<PrecisionE::None> { | |
| static constexpr const char* fmt = ""; | |
| }; | |
| template <> | |
| struct Precision<PrecisionE::Zero> { | |
| static constexpr const char* fmt = ".0"; | |
| }; | |
| template <> | |
| struct Precision<PrecisionE::Small> { | |
| static constexpr const char* fmt = ".10"; | |
| }; | |
| template <> | |
| struct Precision<PrecisionE::Huge> { | |
| // The maximum precision for a minimal sub normal long double is +/- 0x1p-16494. | |
| // This value is always larger than that value forcing the trailing zero path | |
| // to be executed. | |
| static constexpr const char* fmt = ".17000"; | |
| }; | |
| template <class L, class DT, class T, class V, class A, class P> | |
| struct FloatingPoint { | |
| using F = typename Type<T::value>::type; | |
| void run(benchmark::State& state) const { | |
| std::array<F, 1000> data{Value<V::value>::template make_data<F>()}; | |
| std::array<char, 20'000> output; | |
| while (state.KeepRunningBatch(1000)) | |
| for (F value : data) | |
| benchmark::DoNotOptimize(std::format_to(output.begin(), std::string_view{fmt.data(), fmt.size()}, value)); | |
| } | |
| std::string name() const { | |
| return "FloatingPoint" + L::name() + DT::name() + T::name() + V::name() + A::name() + P::name(); | |
| } | |
| static constexpr std::string make_fmt() { | |
| return std::string("{:") + Alignment<A::value>::fmt + Precision<P::value>::fmt + Localization<L::value>::fmt + | |
| DisplayType<DT::value>::fmt + "}"; | |
| } | |
| static constexpr auto fmt = []() { | |
| constexpr size_t s = make_fmt().size(); | |
| std::array<char, s> 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<FloatingPoint, AllLocalizations, AllDisplayTypes, AllTypes, AllValues, AllAlignments, | |
| AllPrecisions>(); | |
| benchmark::RunSpecifiedBenchmarks(); | |
| } |