blob: 22d84afe14ada519f88bba486f4a8770762a9718 [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
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +000076static constexpr char SmallStringLiteral[] = "012345678";
Samuel Benzaquen935e0092019-03-21 16:06:15 +000077
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000078TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
79 switch (D) {
80 case DiffType::Control:
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +000081 return SmallStringLiteral;
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000082 case DiffType::ChangeFirst:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000083 return "-12345678";
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000084 case DiffType::ChangeMiddle:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000085 return "0123-5678";
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000086 case DiffType::ChangeLast:
Samuel Benzaquen935e0092019-03-21 16:06:15 +000087 return "01234567-";
Eric Fiselier718a8ec2018-07-10 04:11:22 +000088 }
89}
Eric Fiselier718a8ec2018-07-10 04:11:22 +000090
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +000091static constexpr char LargeStringLiteral[] =
92 "012345678901234567890123456789012345678901234567890123456789012";
93
Samuel Benzaquencfe3d782018-10-30 15:54:22 +000094TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) {
95#define LARGE_STRING_FIRST "123456789012345678901234567890"
96#define LARGE_STRING_SECOND "234567890123456789012345678901"
97 switch (D) {
98 case DiffType::Control:
99 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
100 case DiffType::ChangeFirst:
101 return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
102 case DiffType::ChangeMiddle:
103 return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2";
104 case DiffType::ChangeLast:
105 return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-";
106 }
107}
108
109TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) {
110#define HUGE_STRING0 "0123456789"
111#define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0
112#define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1
113#define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2
114#define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3
115 switch (D) {
116 case DiffType::Control:
117 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
118 case DiffType::ChangeFirst:
119 return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
120 case DiffType::ChangeMiddle:
121 return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789";
122 case DiffType::ChangeLast:
123 return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-";
124 }
125}
126
127TEST_ALWAYS_INLINE std::string makeString(Length L,
128 DiffType D = DiffType::Control,
129 Opacity O = Opacity::Transparent) {
130 switch (L) {
131 case Length::Empty:
132 return maybeOpaque("", O == Opacity::Opaque);
133 case Length::Small:
134 return maybeOpaque(getSmallString(D), O == Opacity::Opaque);
135 case Length::Large:
136 return maybeOpaque(getLargeString(D), O == Opacity::Opaque);
137 case Length::Huge:
138 return maybeOpaque(getHugeString(D), O == Opacity::Opaque);
139 }
140}
141
142template <class Length, class Opaque>
143struct StringConstructDestroyCStr {
144 static void run(benchmark::State& state) {
145 for (auto _ : state) {
146 benchmark::DoNotOptimize(
147 makeString(Length(), DiffType::Control, Opaque()));
148 }
149 }
150
151 static std::string name() {
152 return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name();
153 }
154};
155
156template <class Length, bool MeasureCopy, bool MeasureDestroy>
157static void StringCopyAndDestroy(benchmark::State& state) {
158 static constexpr size_t NumStrings = 1024;
159 auto Orig = makeString(Length());
160 std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings];
161
162 while (state.KeepRunningBatch(NumStrings)) {
163 if (!MeasureCopy)
164 state.PauseTiming();
165 for (size_t I = 0; I < NumStrings; ++I) {
166 ::new (static_cast<void*>(Storage + I)) std::string(Orig);
167 }
168 if (!MeasureCopy)
169 state.ResumeTiming();
170 if (!MeasureDestroy)
171 state.PauseTiming();
172 for (size_t I = 0; I < NumStrings; ++I) {
173 using S = std::string;
174 reinterpret_cast<S*>(Storage + I)->~S();
175 }
176 if (!MeasureDestroy)
177 state.ResumeTiming();
178 }
179}
180
181template <class Length>
182struct StringCopy {
183 static void run(benchmark::State& state) {
184 StringCopyAndDestroy<Length, true, false>(state);
185 }
186
187 static std::string name() { return "BM_StringCopy" + Length::name(); }
188};
189
190template <class Length>
191struct StringDestroy {
192 static void run(benchmark::State& state) {
193 StringCopyAndDestroy<Length, false, true>(state);
194 }
195
196 static std::string name() { return "BM_StringDestroy" + Length::name(); }
197};
198
199template <class Length>
200struct StringMove {
201 static void run(benchmark::State& state) {
202 // Keep two object locations and move construct back and forth.
Eric Fiselier44966cf2018-11-13 19:16:19 +0000203 std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2];
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000204 using S = std::string;
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000205 size_t I = 0;
Eric Fiselier44966cf2018-11-13 19:16:19 +0000206 S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length()));
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000207 for (auto _ : state) {
208 // Switch locations.
209 I ^= 1;
210 benchmark::DoNotOptimize(Storage);
211 // Move construct into the new location,
Eric Fiselier44966cf2018-11-13 19:16:19 +0000212 S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS));
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000213 // then destroy the old one.
Eric Fiselier44966cf2018-11-13 19:16:19 +0000214 newS->~S();
215 newS = tmpS;
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000216 }
Eric Fiselier44966cf2018-11-13 19:16:19 +0000217 newS->~S();
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000218 }
219
220 static std::string name() { return "BM_StringMove" + Length::name(); }
221};
222
223enum class Relation { Eq, Less, Compare };
224struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> {
225 static constexpr const char* Names[] = {"Eq", "Less", "Compare"};
226};
227
228template <class Rel, class LHLength, class RHLength, class DiffType>
229struct StringRelational {
230 static void run(benchmark::State& state) {
231 auto Lhs = makeString(RHLength());
232 auto Rhs = makeString(LHLength(), DiffType());
233 for (auto _ : state) {
234 benchmark::DoNotOptimize(Lhs);
235 benchmark::DoNotOptimize(Rhs);
236 switch (Rel()) {
237 case Relation::Eq:
238 benchmark::DoNotOptimize(Lhs == Rhs);
239 break;
240 case Relation::Less:
241 benchmark::DoNotOptimize(Lhs < Rhs);
242 break;
243 case Relation::Compare:
244 benchmark::DoNotOptimize(Lhs.compare(Rhs));
245 break;
246 }
247 }
248 }
249
250 static bool skip() {
251 // Eq is commutative, so skip half the matrix.
252 if (Rel() == Relation::Eq && LHLength() > RHLength())
253 return true;
254 // We only care about control when the lengths differ.
255 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
256 return true;
257 // For empty, only control matters.
258 if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control)
259 return true;
260 return false;
261 }
262
263 static std::string name() {
264 return "BM_StringRelational" + Rel::name() + LHLength::name() +
265 RHLength::name() + DiffType::name();
266 }
267};
268
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000269template <class Rel, class LHLength, class RHLength, class DiffType>
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000270struct StringRelationalLiteral {
271 static void run(benchmark::State& state) {
272 auto Lhs = makeString(LHLength(), DiffType());
273 for (auto _ : state) {
274 benchmark::DoNotOptimize(Lhs);
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000275 constexpr const char* Literal = RHLength::value == Length::Empty
276 ? ""
277 : RHLength::value == Length::Small
278 ? SmallStringLiteral
279 : LargeStringLiteral;
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000280 switch (Rel()) {
281 case Relation::Eq:
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000282 benchmark::DoNotOptimize(Lhs == Literal);
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000283 break;
284 case Relation::Less:
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000285 benchmark::DoNotOptimize(Lhs < Literal);
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000286 break;
287 case Relation::Compare:
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000288 benchmark::DoNotOptimize(Lhs.compare(Literal));
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000289 break;
290 }
291 }
292 }
293
294 static bool skip() {
295 // Doesn't matter how they differ if they have different size.
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000296 if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000297 return true;
298 // We don't need huge. Doensn't give anything different than Large.
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000299 if (LHLength() == Length::Huge || RHLength() == Length::Huge)
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000300 return true;
301 return false;
302 }
303
304 static std::string name() {
305 return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() +
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000306 RHLength::name() + DiffType::name();
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000307 }
308};
309
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000310enum class Depth { Shallow, Deep };
311struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> {
312 static constexpr const char* Names[] = {"Shallow", "Deep"};
313};
314
315enum class Temperature { Hot, Cold };
316struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> {
317 static constexpr const char* Names[] = {"Hot", "Cold"};
318};
319
320template <class Temperature, class Depth, class Length>
321struct StringRead {
322 void run(benchmark::State& state) const {
323 static constexpr size_t NumStrings =
324 Temperature() == ::Temperature::Hot
325 ? 1 << 10
326 : /* Enough strings to overflow the cache */ 1 << 20;
327 static_assert((NumStrings & (NumStrings - 1)) == 0,
328 "NumStrings should be a power of two to reduce overhead.");
329
330 std::vector<std::string> Values(NumStrings, makeString(Length()));
331 size_t I = 0;
332 for (auto _ : state) {
333 // Jump long enough to defeat cache locality, and use a value that is
334 // coprime with NumStrings to ensure we visit every element.
335 I = (I + 17) % NumStrings;
336 const auto& V = Values[I];
337
338 // Read everything first. Escaping data() through DoNotOptimize might
339 // cause the compiler to have to recalculate information about `V` due to
340 // aliasing.
341 const char* const Data = V.data();
342 const size_t Size = V.size();
343 benchmark::DoNotOptimize(Data);
344 benchmark::DoNotOptimize(Size);
345 if (Depth() == ::Depth::Deep) {
346 // Read into the payload. This mainly shows the benefit of SSO when the
347 // data is cold.
348 benchmark::DoNotOptimize(*Data);
349 }
350 }
351 }
352
353 static bool skip() {
354 // Huge does not give us anything that Large doesn't have. Skip it.
355 if (Length() == ::Length::Huge) {
356 return true;
357 }
358 return false;
359 }
360
361 std::string name() const {
362 return "BM_StringRead" + Temperature::name() + Depth::name() +
363 Length::name();
364 }
365};
366
367void sanityCheckGeneratedStrings() {
368 for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
369 const auto LhsString = makeString(Lhs);
370 for (auto Rhs :
371 {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
372 if (Lhs > Rhs)
373 continue;
374 const auto RhsString = makeString(Rhs);
375
376 // The smaller one must be a prefix of the larger one.
377 if (RhsString.find(LhsString) != 0) {
378 fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n",
379 static_cast<int>(Lhs), static_cast<int>(Rhs));
380 std::abort();
381 }
382 }
383 }
384 // Verify the autogenerated diffs
385 for (auto L : {Length::Small, Length::Large, Length::Huge}) {
386 const auto Control = makeString(L);
387 const auto Verify = [&](std::string Exp, size_t Pos) {
388 // Only change on the Pos char.
389 if (Control[Pos] != Exp[Pos]) {
390 Exp[Pos] = Control[Pos];
391 if (Control == Exp)
392 return;
393 }
394 fprintf(stderr, "Invalid autogenerated diff with size %d\n",
395 static_cast<int>(L));
396 std::abort();
397 };
398 Verify(makeString(L, DiffType::ChangeFirst), 0);
399 Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2);
400 Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1);
401 }
402}
403
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000404// Some small codegen thunks to easily see generated code.
405bool StringEqString(const std::string& a, const std::string& b) {
406 return a == b;
407}
408bool StringEqCStr(const std::string& a, const char* b) { return a == b; }
409bool CStrEqString(const char* a, const std::string& b) { return a == b; }
410bool StringEqCStrLiteralEmpty(const std::string& a) {
411 return a == "";
412}
413bool StringEqCStrLiteralSmall(const std::string& a) {
414 return a == SmallStringLiteral;
415}
416bool StringEqCStrLiteralLarge(const std::string& a) {
417 return a == LargeStringLiteral;
418}
419
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000420int main(int argc, char** argv) {
421 benchmark::Initialize(&argc, argv);
422 if (benchmark::ReportUnrecognizedArguments(argc, argv))
423 return 1;
424
425 sanityCheckGeneratedStrings();
426
427 makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths,
428 AllOpacity>();
429 makeCartesianProductBenchmark<StringCopy, AllLengths>();
430 makeCartesianProductBenchmark<StringMove, AllLengths>();
431 makeCartesianProductBenchmark<StringDestroy, AllLengths>();
432 makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths,
433 AllLengths, AllDiffTypes>();
Samuel Benzaquen935e0092019-03-21 16:06:15 +0000434 makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations,
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000435 AllLengths, AllLengths, AllDiffTypes>();
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000436 makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths,
437 AllLengths>();
438 benchmark::RunSpecifiedBenchmarks();
Samuel Benzaquen42a76dc2019-04-03 17:40:51 +0000439
440 if (argc < 0) {
441 // ODR-use the functions to force them being generated in the binary.
442 auto functions = std::make_tuple(
443 StringEqString, StringEqCStr, CStrEqString, StringEqCStrLiteralEmpty,
444 StringEqCStrLiteralSmall, StringEqCStrLiteralLarge);
445 printf("%p", &functions);
446 }
Samuel Benzaquencfe3d782018-10-30 15:54:22 +0000447}