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