| //===----------------------------------------------------------------------===// |
| // 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(); |
| } |