1 
2 #include <cstdint>
3 #include <new>
4 #include <vector>
5 
6 #include "CartesianBenchmarks.h"
7 #include "GenerateInput.h"
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.
BM_StringFindNoMatch(benchmark::State & state)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.
BM_StringFindAllMatch(benchmark::State & state)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.
BM_StringFindMatch1(benchmark::State & state)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.
BM_StringFindMatch2(benchmark::State & state)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 
BM_StringCtorDefault(benchmark::State & state)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 SmallStringLiteral[] = "012345678";
77 
getSmallString(DiffType D)78 TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
79   switch (D) {
80     case DiffType::Control:
81       return SmallStringLiteral;
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 static constexpr char LargeStringLiteral[] =
92     "012345678901234567890123456789012345678901234567890123456789012";
93 
getLargeString(DiffType D)94 TEST_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 
getHugeString(DiffType D)109 TEST_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 
getString(Length L,DiffType D=DiffType::Control)127 TEST_ALWAYS_INLINE const char* getString(Length L,
128                                          DiffType D = DiffType::Control) {
129   switch (L) {
130   case Length::Empty:
131     return "";
132   case Length::Small:
133     return getSmallString(D);
134   case Length::Large:
135     return getLargeString(D);
136   case Length::Huge:
137     return getHugeString(D);
138   }
139 }
140 
makeString(Length L,DiffType D=DiffType::Control,Opacity O=Opacity::Transparent)141 TEST_ALWAYS_INLINE std::string makeString(Length L,
142                                           DiffType D = DiffType::Control,
143                                           Opacity O = Opacity::Transparent) {
144   switch (L) {
145   case Length::Empty:
146     return maybeOpaque("", O == Opacity::Opaque);
147   case Length::Small:
148     return maybeOpaque(getSmallString(D), O == Opacity::Opaque);
149   case Length::Large:
150     return maybeOpaque(getLargeString(D), O == Opacity::Opaque);
151   case Length::Huge:
152     return maybeOpaque(getHugeString(D), O == Opacity::Opaque);
153   }
154 }
155 
156 template <class Length, class Opaque>
157 struct StringConstructDestroyCStr {
runStringConstructDestroyCStr158   static void run(benchmark::State& state) {
159     for (auto _ : state) {
160       benchmark::DoNotOptimize(
161           makeString(Length(), DiffType::Control, Opaque()));
162     }
163   }
164 
nameStringConstructDestroyCStr165   static std::string name() {
166     return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name();
167   }
168 };
169 
170 template <class Length, bool MeasureCopy, bool MeasureDestroy>
StringCopyAndDestroy(benchmark::State & state)171 static void StringCopyAndDestroy(benchmark::State& state) {
172   static constexpr size_t NumStrings = 1024;
173   auto Orig = makeString(Length());
174   std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings];
175 
176   while (state.KeepRunningBatch(NumStrings)) {
177     if (!MeasureCopy)
178       state.PauseTiming();
179     for (size_t I = 0; I < NumStrings; ++I) {
180       ::new (static_cast<void*>(Storage + I)) std::string(Orig);
181     }
182     if (!MeasureCopy)
183       state.ResumeTiming();
184     if (!MeasureDestroy)
185       state.PauseTiming();
186     for (size_t I = 0; I < NumStrings; ++I) {
187       using S = std::string;
188       reinterpret_cast<S*>(Storage + I)->~S();
189     }
190     if (!MeasureDestroy)
191       state.ResumeTiming();
192   }
193 }
194 
195 template <class Length>
196 struct StringCopy {
runStringCopy197   static void run(benchmark::State& state) {
198     StringCopyAndDestroy<Length, true, false>(state);
199   }
200 
nameStringCopy201   static std::string name() { return "BM_StringCopy" + Length::name(); }
202 };
203 
204 template <class Length>
205 struct StringDestroy {
runStringDestroy206   static void run(benchmark::State& state) {
207     StringCopyAndDestroy<Length, false, true>(state);
208   }
209 
nameStringDestroy210   static std::string name() { return "BM_StringDestroy" + Length::name(); }
211 };
212 
213 template <class Length>
214 struct StringMove {
runStringMove215   static void run(benchmark::State& state) {
216     // Keep two object locations and move construct back and forth.
217     std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2];
218     using S = std::string;
219     size_t I = 0;
220     S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length()));
221     for (auto _ : state) {
222       // Switch locations.
223       I ^= 1;
224       benchmark::DoNotOptimize(Storage);
225       // Move construct into the new location,
226       S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS));
227       // then destroy the old one.
228       newS->~S();
229       newS = tmpS;
230     }
231     newS->~S();
232   }
233 
nameStringMove234   static std::string name() { return "BM_StringMove" + Length::name(); }
235 };
236 
237 template <class Length, class Opaque>
238 struct StringResizeDefaultInit {
runStringResizeDefaultInit239   static void run(benchmark::State& state) {
240     constexpr bool opaque = Opaque{} == Opacity::Opaque;
241     constexpr int kNumStrings = 4 << 10;
242     size_t length = makeString(Length()).size();
243     std::string strings[kNumStrings];
244     while (state.KeepRunningBatch(kNumStrings)) {
245       state.PauseTiming();
246       for (int i = 0; i < kNumStrings; ++i) {
247         std::string().swap(strings[i]);
248       }
249       benchmark::DoNotOptimize(strings);
250       state.ResumeTiming();
251       for (int i = 0; i < kNumStrings; ++i) {
252         strings[i].__resize_default_init(maybeOpaque(length, opaque));
253       }
254     }
255   }
256 
nameStringResizeDefaultInit257   static std::string name() {
258     return "BM_StringResizeDefaultInit" + Length::name() + Opaque::name();
259   }
260 };
261 
262 template <class Length, class Opaque>
263 struct StringAssignStr {
runStringAssignStr264   static void run(benchmark::State& state) {
265     constexpr bool opaque = Opaque{} == Opacity::Opaque;
266     constexpr int kNumStrings = 4 << 10;
267     std::string src = makeString(Length());
268     std::string strings[kNumStrings];
269     while (state.KeepRunningBatch(kNumStrings)) {
270       state.PauseTiming();
271       for (int i = 0; i < kNumStrings; ++i) {
272         std::string().swap(strings[i]);
273       }
274       benchmark::DoNotOptimize(strings);
275       state.ResumeTiming();
276       for (int i = 0; i < kNumStrings; ++i) {
277         strings[i] = *maybeOpaque(&src, opaque);
278       }
279     }
280   }
281 
nameStringAssignStr282   static std::string name() {
283     return "BM_StringAssignStr" + Length::name() + Opaque::name();
284   }
285 };
286 
287 template <class Length, class Opaque>
288 struct StringAssignAsciiz {
runStringAssignAsciiz289   static void run(benchmark::State& state) {
290     constexpr bool opaque = Opaque{} == Opacity::Opaque;
291     constexpr int kNumStrings = 4 << 10;
292     std::string strings[kNumStrings];
293     while (state.KeepRunningBatch(kNumStrings)) {
294       state.PauseTiming();
295       for (int i = 0; i < kNumStrings; ++i) {
296         std::string().swap(strings[i]);
297       }
298       benchmark::DoNotOptimize(strings);
299       state.ResumeTiming();
300       for (int i = 0; i < kNumStrings; ++i) {
301         strings[i] = maybeOpaque(getString(Length()), opaque);
302       }
303     }
304   }
305 
nameStringAssignAsciiz306   static std::string name() {
307     return "BM_StringAssignAsciiz" + Length::name() + Opaque::name();
308   }
309 };
310 
311 template <class Length, class Opaque>
312 struct StringEraseToEnd {
runStringEraseToEnd313   static void run(benchmark::State& state) {
314     constexpr bool opaque = Opaque{} == Opacity::Opaque;
315     constexpr int kNumStrings = 4 << 10;
316     std::string strings[kNumStrings];
317     const int mid = makeString(Length()).size() / 2;
318     while (state.KeepRunningBatch(kNumStrings)) {
319       state.PauseTiming();
320       for (int i = 0; i < kNumStrings; ++i) {
321         strings[i] = makeString(Length());
322       }
323       benchmark::DoNotOptimize(strings);
324       state.ResumeTiming();
325       for (int i = 0; i < kNumStrings; ++i) {
326         strings[i].erase(maybeOpaque(mid, opaque),
327                          maybeOpaque(std::string::npos, opaque));
328       }
329     }
330   }
331 
nameStringEraseToEnd332   static std::string name() {
333     return "BM_StringEraseToEnd" + Length::name() + Opaque::name();
334   }
335 };
336 
337 template <class Length, class Opaque>
338 struct StringEraseWithMove {
runStringEraseWithMove339   static void run(benchmark::State& state) {
340     constexpr bool opaque = Opaque{} == Opacity::Opaque;
341     constexpr int kNumStrings = 4 << 10;
342     std::string strings[kNumStrings];
343     const int n = makeString(Length()).size() / 2;
344     const int pos = n / 2;
345     while (state.KeepRunningBatch(kNumStrings)) {
346       state.PauseTiming();
347       for (int i = 0; i < kNumStrings; ++i) {
348         strings[i] = makeString(Length());
349       }
350       benchmark::DoNotOptimize(strings);
351       state.ResumeTiming();
352       for (int i = 0; i < kNumStrings; ++i) {
353         strings[i].erase(maybeOpaque(pos, opaque), maybeOpaque(n, opaque));
354       }
355     }
356   }
357 
nameStringEraseWithMove358   static std::string name() {
359     return "BM_StringEraseWithMove" + Length::name() + Opaque::name();
360   }
361 };
362 
363 template <class Opaque>
364 struct StringAssignAsciizMix {
runStringAssignAsciizMix365   static void run(benchmark::State& state) {
366     constexpr auto O = Opaque{};
367     constexpr auto D = DiffType::Control;
368     constexpr int kNumStrings = 4 << 10;
369     std::string strings[kNumStrings];
370     while (state.KeepRunningBatch(kNumStrings)) {
371       state.PauseTiming();
372       for (int i = 0; i < kNumStrings; ++i) {
373         std::string().swap(strings[i]);
374       }
375       benchmark::DoNotOptimize(strings);
376       state.ResumeTiming();
377       for (int i = 0; i < kNumStrings - 7; i += 8) {
378         strings[i + 0] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
379         strings[i + 1] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
380         strings[i + 2] = maybeOpaque(getLargeString(D), O == Opacity::Opaque);
381         strings[i + 3] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
382         strings[i + 4] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
383         strings[i + 5] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
384         strings[i + 6] = maybeOpaque(getLargeString(D), O == Opacity::Opaque);
385         strings[i + 7] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
386       }
387     }
388   }
389 
nameStringAssignAsciizMix390   static std::string name() {
391     return "BM_StringAssignAsciizMix" + Opaque::name();
392   }
393 };
394 
395 enum class Relation { Eq, Less, Compare };
396 struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> {
397   static constexpr const char* Names[] = {"Eq", "Less", "Compare"};
398 };
399 
400 template <class Rel, class LHLength, class RHLength, class DiffType>
401 struct StringRelational {
runStringRelational402   static void run(benchmark::State& state) {
403     auto Lhs = makeString(RHLength());
404     auto Rhs = makeString(LHLength(), DiffType());
405     for (auto _ : state) {
406       benchmark::DoNotOptimize(Lhs);
407       benchmark::DoNotOptimize(Rhs);
408       switch (Rel()) {
409       case Relation::Eq:
410         benchmark::DoNotOptimize(Lhs == Rhs);
411         break;
412       case Relation::Less:
413         benchmark::DoNotOptimize(Lhs < Rhs);
414         break;
415       case Relation::Compare:
416         benchmark::DoNotOptimize(Lhs.compare(Rhs));
417         break;
418       }
419     }
420   }
421 
skipStringRelational422   static bool skip() {
423     // Eq is commutative, so skip half the matrix.
424     if (Rel() == Relation::Eq && LHLength() > RHLength())
425       return true;
426     // We only care about control when the lengths differ.
427     if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
428       return true;
429     // For empty, only control matters.
430     if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control)
431       return true;
432     return false;
433   }
434 
nameStringRelational435   static std::string name() {
436     return "BM_StringRelational" + Rel::name() + LHLength::name() +
437            RHLength::name() + DiffType::name();
438   }
439 };
440 
441 template <class Rel, class LHLength, class RHLength, class DiffType>
442 struct StringRelationalLiteral {
runStringRelationalLiteral443   static void run(benchmark::State& state) {
444     auto Lhs = makeString(LHLength(), DiffType());
445     for (auto _ : state) {
446       benchmark::DoNotOptimize(Lhs);
447       constexpr const char* Literal = RHLength::value == Length::Empty
448                                           ? ""
449                                           : RHLength::value == Length::Small
450                                                 ? SmallStringLiteral
451                                                 : LargeStringLiteral;
452       switch (Rel()) {
453       case Relation::Eq:
454         benchmark::DoNotOptimize(Lhs == Literal);
455         break;
456       case Relation::Less:
457         benchmark::DoNotOptimize(Lhs < Literal);
458         break;
459       case Relation::Compare:
460         benchmark::DoNotOptimize(Lhs.compare(Literal));
461         break;
462       }
463     }
464   }
465 
skipStringRelationalLiteral466   static bool skip() {
467     // Doesn't matter how they differ if they have different size.
468     if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
469       return true;
470     // We don't need huge. Doensn't give anything different than Large.
471     if (LHLength() == Length::Huge || RHLength() == Length::Huge)
472       return true;
473     return false;
474   }
475 
nameStringRelationalLiteral476   static std::string name() {
477     return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() +
478            RHLength::name() + DiffType::name();
479   }
480 };
481 
482 enum class Depth { Shallow, Deep };
483 struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> {
484   static constexpr const char* Names[] = {"Shallow", "Deep"};
485 };
486 
487 enum class Temperature { Hot, Cold };
488 struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> {
489   static constexpr const char* Names[] = {"Hot", "Cold"};
490 };
491 
492 template <class Temperature, class Depth, class Length>
493 struct StringRead {
runStringRead494   void run(benchmark::State& state) const {
495     static constexpr size_t NumStrings =
496         Temperature() == ::Temperature::Hot
497             ? 1 << 10
498             : /* Enough strings to overflow the cache */ 1 << 20;
499     static_assert((NumStrings & (NumStrings - 1)) == 0,
500                   "NumStrings should be a power of two to reduce overhead.");
501 
502     std::vector<std::string> Values(NumStrings, makeString(Length()));
503     size_t I = 0;
504     for (auto _ : state) {
505       // Jump long enough to defeat cache locality, and use a value that is
506       // coprime with NumStrings to ensure we visit every element.
507       I = (I + 17) % NumStrings;
508       const auto& V = Values[I];
509 
510       // Read everything first. Escaping data() through DoNotOptimize might
511       // cause the compiler to have to recalculate information about `V` due to
512       // aliasing.
513       const char* const Data = V.data();
514       const size_t Size = V.size();
515       benchmark::DoNotOptimize(Data);
516       benchmark::DoNotOptimize(Size);
517       if (Depth() == ::Depth::Deep) {
518         // Read into the payload. This mainly shows the benefit of SSO when the
519         // data is cold.
520         benchmark::DoNotOptimize(*Data);
521       }
522     }
523   }
524 
skipStringRead525   static bool skip() {
526     // Huge does not give us anything that Large doesn't have. Skip it.
527     if (Length() == ::Length::Huge) {
528       return true;
529     }
530     return false;
531   }
532 
nameStringRead533   std::string name() const {
534     return "BM_StringRead" + Temperature::name() + Depth::name() +
535            Length::name();
536   }
537 };
538 
sanityCheckGeneratedStrings()539 void sanityCheckGeneratedStrings() {
540   for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
541     const auto LhsString = makeString(Lhs);
542     for (auto Rhs :
543          {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
544       if (Lhs > Rhs)
545         continue;
546       const auto RhsString = makeString(Rhs);
547 
548       // The smaller one must be a prefix of the larger one.
549       if (RhsString.find(LhsString) != 0) {
550         fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n",
551                 static_cast<int>(Lhs), static_cast<int>(Rhs));
552         std::abort();
553       }
554     }
555   }
556   // Verify the autogenerated diffs
557   for (auto L : {Length::Small, Length::Large, Length::Huge}) {
558     const auto Control = makeString(L);
559     const auto Verify = [&](std::string Exp, size_t Pos) {
560       // Only change on the Pos char.
561       if (Control[Pos] != Exp[Pos]) {
562         Exp[Pos] = Control[Pos];
563         if (Control == Exp)
564           return;
565       }
566       fprintf(stderr, "Invalid autogenerated diff with size %d\n",
567               static_cast<int>(L));
568       std::abort();
569     };
570     Verify(makeString(L, DiffType::ChangeFirst), 0);
571     Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2);
572     Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1);
573   }
574 }
575 
576 // Some small codegen thunks to easily see generated code.
StringEqString(const std::string & a,const std::string & b)577 bool StringEqString(const std::string& a, const std::string& b) {
578   return a == b;
579 }
StringEqCStr(const std::string & a,const char * b)580 bool StringEqCStr(const std::string& a, const char* b) { return a == b; }
CStrEqString(const char * a,const std::string & b)581 bool CStrEqString(const char* a, const std::string& b) { return a == b; }
StringEqCStrLiteralEmpty(const std::string & a)582 bool StringEqCStrLiteralEmpty(const std::string& a) {
583   return a == "";
584 }
StringEqCStrLiteralSmall(const std::string & a)585 bool StringEqCStrLiteralSmall(const std::string& a) {
586   return a == SmallStringLiteral;
587 }
StringEqCStrLiteralLarge(const std::string & a)588 bool StringEqCStrLiteralLarge(const std::string& a) {
589   return a == LargeStringLiteral;
590 }
591 
main(int argc,char ** argv)592 int main(int argc, char** argv) {
593   benchmark::Initialize(&argc, argv);
594   if (benchmark::ReportUnrecognizedArguments(argc, argv))
595     return 1;
596 
597   sanityCheckGeneratedStrings();
598 
599   makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths,
600                                 AllOpacity>();
601 
602   makeCartesianProductBenchmark<StringAssignStr, AllLengths, AllOpacity>();
603   makeCartesianProductBenchmark<StringAssignAsciiz, AllLengths, AllOpacity>();
604   makeCartesianProductBenchmark<StringAssignAsciizMix, AllOpacity>();
605 
606   makeCartesianProductBenchmark<StringCopy, AllLengths>();
607   makeCartesianProductBenchmark<StringMove, AllLengths>();
608   makeCartesianProductBenchmark<StringDestroy, AllLengths>();
609   makeCartesianProductBenchmark<StringResizeDefaultInit, AllLengths,
610                                 AllOpacity>();
611   makeCartesianProductBenchmark<StringEraseToEnd, AllLengths, AllOpacity>();
612   makeCartesianProductBenchmark<StringEraseWithMove, AllLengths, AllOpacity>();
613   makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths,
614                                 AllLengths, AllDiffTypes>();
615   makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations,
616                                 AllLengths, AllLengths, AllDiffTypes>();
617   makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths,
618                                 AllLengths>();
619   benchmark::RunSpecifiedBenchmarks();
620 
621   if (argc < 0) {
622     // ODR-use the functions to force them being generated in the binary.
623     auto functions = std::make_tuple(
624         StringEqString, StringEqCStr, CStrEqString, StringEqCStrLiteralEmpty,
625         StringEqCStrLiteralSmall, StringEqCStrLiteralLarge);
626     printf("%p", &functions);
627   }
628 }
629