blob: d7f919b1499187c44f15df898aaf7302b7680d72 [file] [log] [blame]
Sebastian Pop65115202016-12-30 18:01:36 +00001
Samuel Benzaquencfe3d782018-10-30 15:54:22 +00002#include <cstdint>
3#include <new>
4#include <vector>
5
6#include "CartesianBenchmarks.hpp"
Sebastian Pop65115202016-12-30 18:01:36 +00007#include "GenerateInput.hpp"
Samuel Benzaquencfe3d782018-10-30 15:54:22 +00008#include "benchmark/benchmark.h"
9#include "test_macros.h"
Sebastian Pop65115202016-12-30 18:01:36 +000010
11constexpr std::size_t MAX_STRING_LEN = 8 << 14;
12
13// Benchmark when there is no match.
14static void BM_StringFindNoMatch(benchmark::State &state) {
15 std::string s1(state.range(0), '-');
16 std::string s2(8, '*');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000017 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000018 benchmark::DoNotOptimize(s1.find(s2));
19}
20BENCHMARK(BM_StringFindNoMatch)->Range(10, MAX_STRING_LEN);
21
22// Benchmark when the string matches first time.
23static void BM_StringFindAllMatch(benchmark::State &state) {
24 std::string s1(MAX_STRING_LEN, '-');
25 std::string s2(state.range(0), '-');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000026 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000027 benchmark::DoNotOptimize(s1.find(s2));
28}
29BENCHMARK(BM_StringFindAllMatch)->Range(1, MAX_STRING_LEN);
30
31// Benchmark when the string matches somewhere in the end.
32static void BM_StringFindMatch1(benchmark::State &state) {
33 std::string s1(MAX_STRING_LEN / 2, '*');
34 s1 += std::string(state.range(0), '-');
35 std::string s2(state.range(0), '-');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000036 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000037 benchmark::DoNotOptimize(s1.find(s2));
38}
39BENCHMARK(BM_StringFindMatch1)->Range(1, MAX_STRING_LEN / 4);
40
41// Benchmark when the string matches somewhere from middle to the end.
42static void BM_StringFindMatch2(benchmark::State &state) {
43 std::string s1(MAX_STRING_LEN / 2, '*');
44 s1 += std::string(state.range(0), '-');
45 s1 += std::string(state.range(0), '*');
46 std::string s2(state.range(0), '-');
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000047 for (auto _ : state)
Sebastian Pop65115202016-12-30 18:01:36 +000048 benchmark::DoNotOptimize(s1.find(s2));
49}
50BENCHMARK(BM_StringFindMatch2)->Range(1, MAX_STRING_LEN / 4);
51
Eric Fiselier718a8ec2018-07-10 04:11:22 +000052static void BM_StringCtorDefault(benchmark::State &state) {
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000053 for (auto _ : state) {
54 std::string Default;
55 benchmark::DoNotOptimize(Default);
Eric Fiselier718a8ec2018-07-10 04:11:22 +000056 }
57}
58BENCHMARK(BM_StringCtorDefault);
59
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000060enum class Length { Empty, Small, Large, Huge };
61struct AllLengths : EnumValuesAsTuple<AllLengths, Length, 4> {
62 static constexpr const char* Names[] = {"Empty", "Small", "Large", "Huge"};
63};
64
65enum class Opacity { Opaque, Transparent };
66struct AllOpacity : EnumValuesAsTuple<AllOpacity, Opacity, 2> {
67 static constexpr const char* Names[] = {"Opaque", "Transparent"};
68};
69
70enum class DiffType { Control, ChangeFirst, ChangeMiddle, ChangeLast };
71struct AllDiffTypes : EnumValuesAsTuple<AllDiffTypes, DiffType, 4> {
72 static constexpr const char* Names[] = {"Control", "ChangeFirst",
73 "ChangeMiddle", "ChangeLast"};
74};
75
76TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
77 switch (D) {
78 case DiffType::Control:
79 return "0123456";
80 case DiffType::ChangeFirst:
81 return "-123456";
82 case DiffType::ChangeMiddle:
83 return "012-456";
84 case DiffType::ChangeLast:
85 return "012345-";
Eric Fiselier718a8ec2018-07-10 04:11:22 +000086 }
87}
Eric Fiselier718a8ec2018-07-10 04:11:22 +000088
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000089TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) {
90#define LARGE_STRING_FIRST "123456789012345678901234567890"
91#define LARGE_STRING_SECOND "234567890123456789012345678901"
92 switch (D) {
93 case DiffType::Control:
94 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
95 case DiffType::ChangeFirst:
96 return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
97 case DiffType::ChangeMiddle:
98 return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2";
99 case DiffType::ChangeLast:
100 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-";
101 }
102}
103
104TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) {
105#define HUGE_STRING0 "0123456789"
106#define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0
107#define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1
108#define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2
109#define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3
110 switch (D) {
111 case DiffType::Control:
112 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
113 case DiffType::ChangeFirst:
114 return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
115 case DiffType::ChangeMiddle:
116 return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789";
117 case DiffType::ChangeLast:
118 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-";
119 }
120}
121
122TEST_ALWAYS_INLINE std::string makeString(Length L,
123 DiffType D = DiffType::Control,
124 Opacity O = Opacity::Transparent) {
125 switch (L) {
126 case Length::Empty:
127 return maybeOpaque("", O == Opacity::Opaque);
128 case Length::Small:
129 return maybeOpaque(getSmallString(D), O == Opacity::Opaque);
130 case Length::Large:
131 return maybeOpaque(getLargeString(D), O == Opacity::Opaque);
132 case Length::Huge:
133 return maybeOpaque(getHugeString(D), O == Opacity::Opaque);
134 }
135}
136
137template <class Length, class Opaque>
138struct StringConstructDestroyCStr {
139 static void run(benchmark::State& state) {
140 for (auto _ : state) {
141 benchmark::DoNotOptimize(
142 makeString(Length(), DiffType::Control, Opaque()));
143 }
144 }
145
146 static std::string name() {
147 return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name();
148 }
149};
150
151template <class Length, bool MeasureCopy, bool MeasureDestroy>
152static void StringCopyAndDestroy(benchmark::State& state) {
153 static constexpr size_t NumStrings = 1024;
154 auto Orig = makeString(Length());
155 std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings];
156
157 while (state.KeepRunningBatch(NumStrings)) {
158 if (!MeasureCopy)
159 state.PauseTiming();
160 for (size_t I = 0; I < NumStrings; ++I) {
161 ::new (static_cast<void*>(Storage + I)) std::string(Orig);
162 }
163 if (!MeasureCopy)
164 state.ResumeTiming();
165 if (!MeasureDestroy)
166 state.PauseTiming();
167 for (size_t I = 0; I < NumStrings; ++I) {
168 using S = std::string;
169 reinterpret_cast<S*>(Storage + I)->~S();
170 }
171 if (!MeasureDestroy)
172 state.ResumeTiming();
173 }
174}
175
176template <class Length>
177struct StringCopy {
178 static void run(benchmark::State& state) {
179 StringCopyAndDestroy<Length, true, false>(state);
180 }
181
182 static std::string name() { return "BM_StringCopy" + Length::name(); }
183};
184
185template <class Length>
186struct StringDestroy {
187 static void run(benchmark::State& state) {
188 StringCopyAndDestroy<Length, false, true>(state);
189 }
190
191 static std::string name() { return "BM_StringDestroy" + Length::name(); }
192};
193
194template <class Length>
195struct StringMove {
196 static void run(benchmark::State& state) {
197 // Keep two object locations and move construct back and forth.
198 std::aligned_storage<sizeof(std::string)>::type Storage[2];
199 using S = std::string;
200 S* Data = reinterpret_cast<S*>(Storage);
201 size_t I = 0;
202 new (static_cast<void*>(Data)) std::string(makeString(Length()));
203 for (auto _ : state) {
204 // Switch locations.
205 I ^= 1;
206 benchmark::DoNotOptimize(Storage);
207 // Move construct into the new location,
208 new (static_cast<void*>(Storage + I)) S(std::move(Data[I ^ 1]));
209 // then destroy the old one.
210 Data[I ^ 1].~S();
211 }
212 Data[I].~S();
213 }
214
215 static std::string name() { return "BM_StringMove" + Length::name(); }
216};
217
218enum class Relation { Eq, Less, Compare };
219struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> {
220 static constexpr const char* Names[] = {"Eq", "Less", "Compare"};
221};
222
223template <class Rel, class LHLength, class RHLength, class DiffType>
224struct StringRelational {
225 static void run(benchmark::State& state) {
226 auto Lhs = makeString(RHLength());
227 auto Rhs = makeString(LHLength(), DiffType());
228 for (auto _ : state) {
229 benchmark::DoNotOptimize(Lhs);
230 benchmark::DoNotOptimize(Rhs);
231 switch (Rel()) {
232 case Relation::Eq:
233 benchmark::DoNotOptimize(Lhs == Rhs);
234 break;
235 case Relation::Less:
236 benchmark::DoNotOptimize(Lhs < Rhs);
237 break;
238 case Relation::Compare:
239 benchmark::DoNotOptimize(Lhs.compare(Rhs));
240 break;
241 }
242 }
243 }
244
245 static bool skip() {
246 // Eq is commutative, so skip half the matrix.
247 if (Rel() == Relation::Eq && LHLength() > RHLength())
248 return true;
249 // We only care about control when the lengths differ.
250 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
251 return true;
252 // For empty, only control matters.
253 if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control)
254 return true;
255 return false;
256 }
257
258 static std::string name() {
259 return "BM_StringRelational" + Rel::name() + LHLength::name() +
260 RHLength::name() + DiffType::name();
261 }
262};
263
264enum class Depth { Shallow, Deep };
265struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> {
266 static constexpr const char* Names[] = {"Shallow", "Deep"};
267};
268
269enum class Temperature { Hot, Cold };
270struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> {
271 static constexpr const char* Names[] = {"Hot", "Cold"};
272};
273
274template <class Temperature, class Depth, class Length>
275struct StringRead {
276 void run(benchmark::State& state) const {
277 static constexpr size_t NumStrings =
278 Temperature() == ::Temperature::Hot
279 ? 1 << 10
280 : /* Enough strings to overflow the cache */ 1 << 20;
281 static_assert((NumStrings & (NumStrings - 1)) == 0,
282 "NumStrings should be a power of two to reduce overhead.");
283
284 std::vector<std::string> Values(NumStrings, makeString(Length()));
285 size_t I = 0;
286 for (auto _ : state) {
287 // Jump long enough to defeat cache locality, and use a value that is
288 // coprime with NumStrings to ensure we visit every element.
289 I = (I + 17) % NumStrings;
290 const auto& V = Values[I];
291
292 // Read everything first. Escaping data() through DoNotOptimize might
293 // cause the compiler to have to recalculate information about `V` due to
294 // aliasing.
295 const char* const Data = V.data();
296 const size_t Size = V.size();
297 benchmark::DoNotOptimize(Data);
298 benchmark::DoNotOptimize(Size);
299 if (Depth() == ::Depth::Deep) {
300 // Read into the payload. This mainly shows the benefit of SSO when the
301 // data is cold.
302 benchmark::DoNotOptimize(*Data);
303 }
304 }
305 }
306
307 static bool skip() {
308 // Huge does not give us anything that Large doesn't have. Skip it.
309 if (Length() == ::Length::Huge) {
310 return true;
311 }
312 return false;
313 }
314
315 std::string name() const {
316 return "BM_StringRead" + Temperature::name() + Depth::name() +
317 Length::name();
318 }
319};
320
321void sanityCheckGeneratedStrings() {
322 for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
323 const auto LhsString = makeString(Lhs);
324 for (auto Rhs :
325 {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
326 if (Lhs > Rhs)
327 continue;
328 const auto RhsString = makeString(Rhs);
329
330 // The smaller one must be a prefix of the larger one.
331 if (RhsString.find(LhsString) != 0) {
332 fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n",
333 static_cast<int>(Lhs), static_cast<int>(Rhs));
334 std::abort();
335 }
336 }
337 }
338 // Verify the autogenerated diffs
339 for (auto L : {Length::Small, Length::Large, Length::Huge}) {
340 const auto Control = makeString(L);
341 const auto Verify = [&](std::string Exp, size_t Pos) {
342 // Only change on the Pos char.
343 if (Control[Pos] != Exp[Pos]) {
344 Exp[Pos] = Control[Pos];
345 if (Control == Exp)
346 return;
347 }
348 fprintf(stderr, "Invalid autogenerated diff with size %d\n",
349 static_cast<int>(L));
350 std::abort();
351 };
352 Verify(makeString(L, DiffType::ChangeFirst), 0);
353 Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2);
354 Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1);
355 }
356}
357
358int main(int argc, char** argv) {
359 benchmark::Initialize(&argc, argv);
360 if (benchmark::ReportUnrecognizedArguments(argc, argv))
361 return 1;
362
363 sanityCheckGeneratedStrings();
364
365 makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths,
366 AllOpacity>();
367 makeCartesianProductBenchmark<StringCopy, AllLengths>();
368 makeCartesianProductBenchmark<StringMove, AllLengths>();
369 makeCartesianProductBenchmark<StringDestroy, AllLengths>();
370 makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths,
371 AllLengths, AllDiffTypes>();
372 makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths,
373 AllLengths>();
374 benchmark::RunSpecifiedBenchmarks();
375}