1 | //===----------------------------------------------------------------------===// |
2 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
3 | // See https://llvm.org/LICENSE.txt for license information. |
4 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
5 | // |
6 | //===----------------------------------------------------------------------===// |
7 | |
8 | #include <array> |
9 | #include <format> |
10 | #include <random> |
11 | |
12 | #include "CartesianBenchmarks.h" |
13 | #include "benchmark/benchmark.h" |
14 | |
15 | // Tests the full range of the value. |
16 | template <class T> |
17 | static std::array<T, 1000> generate(std::uniform_int_distribution<T> distribution = std::uniform_int_distribution<T>{ |
18 | std::numeric_limits<T>::min(), std::numeric_limits<T>::max()}) { |
19 | std::mt19937 generator; |
20 | std::array<T, 1000> result; |
21 | std::generate_n(result.begin(), result.size(), [&] { return distribution(generator); }); |
22 | return result; |
23 | } |
24 | |
25 | template <class T> |
26 | static void BM_Basic(benchmark::State& state) { |
27 | std::array data{generate<T>()}; |
28 | std::array<char, 100> output; |
29 | |
30 | while (state.KeepRunningBatch(n: data.size())) |
31 | for (auto value : data) |
32 | benchmark::DoNotOptimize(std::format_to(output.begin(), "{}" , value)); |
33 | } |
34 | BENCHMARK_TEMPLATE(BM_Basic, uint32_t); |
35 | BENCHMARK_TEMPLATE(BM_Basic, int32_t); |
36 | BENCHMARK_TEMPLATE(BM_Basic, uint64_t); |
37 | BENCHMARK_TEMPLATE(BM_Basic, int64_t); |
38 | |
39 | // Ideally the low values of a 128-bit value are all dispatched to a 64-bit routine. |
40 | template <class T> |
41 | static void BM_BasicLow(benchmark::State& state) { |
42 | using U = std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>; |
43 | std::array data{ |
44 | generate<T>(std::uniform_int_distribution<T>{std::numeric_limits<U>::min(), std::numeric_limits<U>::max()})}; |
45 | std::array<char, 100> output; |
46 | |
47 | while (state.KeepRunningBatch(n: data.size())) |
48 | for (auto value : data) |
49 | benchmark::DoNotOptimize(std::format_to(output.begin(), "{}" , value)); |
50 | } |
51 | BENCHMARK_TEMPLATE(BM_BasicLow, __uint128_t); |
52 | BENCHMARK_TEMPLATE(BM_BasicLow, __int128_t); |
53 | |
54 | BENCHMARK_TEMPLATE(BM_Basic, __uint128_t); |
55 | BENCHMARK_TEMPLATE(BM_Basic, __int128_t); |
56 | |
57 | // *** Localization *** |
58 | enum class LocalizationE { False, True }; |
59 | struct AllLocalizations : EnumValuesAsTuple<AllLocalizations, LocalizationE, 2> { |
60 | static constexpr const char* Names[] = {"LocFalse" , "LocTrue" }; |
61 | }; |
62 | |
63 | template <LocalizationE E> |
64 | struct Localization {}; |
65 | |
66 | template <> |
67 | struct Localization<LocalizationE::False> { |
68 | static constexpr const char* fmt = "" ; |
69 | }; |
70 | |
71 | template <> |
72 | struct Localization<LocalizationE::True> { |
73 | static constexpr const char* fmt = "L" ; |
74 | }; |
75 | |
76 | // *** Base *** |
77 | enum class BaseE { |
78 | Binary, |
79 | Octal, |
80 | Decimal, |
81 | Hex, |
82 | HexUpper, |
83 | }; |
84 | struct AllBases : EnumValuesAsTuple<AllBases, BaseE, 5> { |
85 | static constexpr const char* Names[] = {"BaseBin" , "BaseOct" , "BaseDec" , "BaseHex" , "BaseHexUpper" }; |
86 | }; |
87 | |
88 | template <BaseE E> |
89 | struct Base {}; |
90 | |
91 | template <> |
92 | struct Base<BaseE::Binary> { |
93 | static constexpr const char* fmt = "b" ; |
94 | }; |
95 | |
96 | template <> |
97 | struct Base<BaseE::Octal> { |
98 | static constexpr const char* fmt = "o" ; |
99 | }; |
100 | |
101 | template <> |
102 | struct Base<BaseE::Decimal> { |
103 | static constexpr const char* fmt = "d" ; |
104 | }; |
105 | |
106 | template <> |
107 | struct Base<BaseE::Hex> { |
108 | static constexpr const char* fmt = "x" ; |
109 | }; |
110 | |
111 | template <> |
112 | struct Base<BaseE::HexUpper> { |
113 | static constexpr const char* fmt = "X" ; |
114 | }; |
115 | |
116 | // *** Types *** |
117 | enum class TypeE { Int64, Uint64 }; |
118 | struct AllTypes : EnumValuesAsTuple<AllTypes, TypeE, 2> { |
119 | static constexpr const char* Names[] = {"Int64" , "Uint64" }; |
120 | }; |
121 | |
122 | template <TypeE E> |
123 | struct Type {}; |
124 | |
125 | template <> |
126 | struct Type<TypeE::Int64> { |
127 | using type = int64_t; |
128 | |
129 | static std::array<type, 1000> make_data() { return generate<type>(); } |
130 | }; |
131 | |
132 | template <> |
133 | struct Type<TypeE::Uint64> { |
134 | using type = uint64_t; |
135 | |
136 | static std::array<type, 1000> make_data() { return generate<type>(); } |
137 | }; |
138 | |
139 | // *** Alignment *** |
140 | enum class AlignmentE { None, Left, Center, Right, ZeroPadding }; |
141 | struct AllAlignments : EnumValuesAsTuple<AllAlignments, AlignmentE, 5> { |
142 | static constexpr const char* Names[] = { |
143 | "AlignNone" , "AlignmentLeft" , "AlignmentCenter" , "AlignmentRight" , "ZeroPadding" }; |
144 | }; |
145 | |
146 | template <AlignmentE E> |
147 | struct Alignment {}; |
148 | |
149 | template <> |
150 | struct Alignment<AlignmentE::None> { |
151 | static constexpr const char* fmt = "" ; |
152 | }; |
153 | |
154 | template <> |
155 | struct Alignment<AlignmentE::Left> { |
156 | static constexpr const char* fmt = "0<512" ; |
157 | }; |
158 | |
159 | template <> |
160 | struct Alignment<AlignmentE::Center> { |
161 | static constexpr const char* fmt = "0^512" ; |
162 | }; |
163 | |
164 | template <> |
165 | struct Alignment<AlignmentE::Right> { |
166 | static constexpr const char* fmt = "0>512" ; |
167 | }; |
168 | |
169 | template <> |
170 | struct Alignment<AlignmentE::ZeroPadding> { |
171 | static constexpr const char* fmt = "0512" ; |
172 | }; |
173 | |
174 | template <class L, class B, class T, class A> |
175 | struct Integral { |
176 | void run(benchmark::State& state) const { |
177 | std::array data{Type<T::value>::make_data()}; |
178 | std::array<char, 512> output; |
179 | |
180 | while (state.KeepRunningBatch(n: data.size())) |
181 | for (auto value : data) |
182 | benchmark::DoNotOptimize(std::format_to(output.begin(), std::string_view{fmt.data(), fmt.size()}, value)); |
183 | } |
184 | |
185 | std::string name() const { return "Integral" + L::name() + B::name() + A::name() + T::name(); } |
186 | |
187 | static constexpr std::string make_fmt() { |
188 | return std::string("{:" ) + Alignment<A::value>::fmt + Localization<L::value>::fmt + Base<B::value>::fmt + "}" ; |
189 | } |
190 | |
191 | static constexpr auto fmt = []() { |
192 | constexpr size_t s = make_fmt().size(); |
193 | std::array<char, s> r; |
194 | std::ranges::copy(make_fmt(), r.begin()); |
195 | return r; |
196 | }(); |
197 | }; |
198 | |
199 | int main(int argc, char** argv) { |
200 | benchmark::Initialize(argc: &argc, argv); |
201 | if (benchmark::ReportUnrecognizedArguments(argc, argv)) |
202 | return 1; |
203 | |
204 | makeCartesianProductBenchmark<Integral, AllLocalizations, AllBases, AllTypes, AllAlignments>(); |
205 | |
206 | benchmark::RunSpecifiedBenchmarks(); |
207 | } |
208 | |