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