xref: /aosp_15_r20/external/cronet/third_party/libc++/src/benchmarks/string.bench.cpp (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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", "ChangeMiddle", "ChangeLast"};
73 };
74 
75 static constexpr char SmallStringLiteral[] = "012345678";
76 
getSmallString(DiffType D)77 TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
78   switch (D) {
79   case DiffType::Control:
80     return SmallStringLiteral;
81   case DiffType::ChangeFirst:
82     return "-12345678";
83   case DiffType::ChangeMiddle:
84     return "0123-5678";
85   case DiffType::ChangeLast:
86     return "01234567-";
87   }
88 }
89 
90 static constexpr char LargeStringLiteral[] = "012345678901234567890123456789012345678901234567890123456789012";
91 
getLargeString(DiffType D)92 TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) {
93 #define LARGE_STRING_FIRST "123456789012345678901234567890"
94 #define LARGE_STRING_SECOND "234567890123456789012345678901"
95   switch (D) {
96   case DiffType::Control:
97     return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
98   case DiffType::ChangeFirst:
99     return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
100   case DiffType::ChangeMiddle:
101     return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2";
102   case DiffType::ChangeLast:
103     return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-";
104   }
105 }
106 
getHugeString(DiffType D)107 TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) {
108 #define HUGE_STRING0 "0123456789"
109 #define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0
110 #define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1
111 #define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2
112 #define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3
113   switch (D) {
114   case DiffType::Control:
115     return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
116   case DiffType::ChangeFirst:
117     return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
118   case DiffType::ChangeMiddle:
119     return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789";
120   case DiffType::ChangeLast:
121     return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-";
122   }
123 }
124 
getString(Length L,DiffType D=DiffType::Control)125 TEST_ALWAYS_INLINE const char* getString(Length L, DiffType D = DiffType::Control) {
126   switch (L) {
127   case Length::Empty:
128     return "";
129   case Length::Small:
130     return getSmallString(D);
131   case Length::Large:
132     return getLargeString(D);
133   case Length::Huge:
134     return getHugeString(D);
135   }
136 }
137 
makeString(Length L,DiffType D=DiffType::Control,Opacity O=Opacity::Transparent)138 TEST_ALWAYS_INLINE std::string makeString(Length L, DiffType D = DiffType::Control, Opacity O = Opacity::Transparent) {
139   switch (L) {
140   case Length::Empty:
141     return maybeOpaque("", O == Opacity::Opaque);
142   case Length::Small:
143     return maybeOpaque(getSmallString(D), O == Opacity::Opaque);
144   case Length::Large:
145     return maybeOpaque(getLargeString(D), O == Opacity::Opaque);
146   case Length::Huge:
147     return maybeOpaque(getHugeString(D), O == Opacity::Opaque);
148   }
149 }
150 
151 template <class Length, class Opaque>
152 struct StringConstructDestroyCStr {
runStringConstructDestroyCStr153   static void run(benchmark::State& state) {
154     for (auto _ : state) {
155       benchmark::DoNotOptimize(makeString(Length(), DiffType::Control, Opaque()));
156     }
157   }
158 
nameStringConstructDestroyCStr159   static std::string name() { return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name(); }
160 };
161 
162 template <class Length, bool MeasureCopy, bool MeasureDestroy>
StringCopyAndDestroy(benchmark::State & state)163 static void StringCopyAndDestroy(benchmark::State& state) {
164   static constexpr size_t NumStrings = 1024;
165   auto Orig                          = makeString(Length());
166   std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings];
167 
168   while (state.KeepRunningBatch(NumStrings)) {
169     if (!MeasureCopy)
170       state.PauseTiming();
171     for (size_t I = 0; I < NumStrings; ++I) {
172       ::new (static_cast<void*>(Storage + I)) std::string(Orig);
173     }
174     if (!MeasureCopy)
175       state.ResumeTiming();
176     if (!MeasureDestroy)
177       state.PauseTiming();
178     for (size_t I = 0; I < NumStrings; ++I) {
179       using S = std::string;
180       reinterpret_cast<S*>(Storage + I)->~S();
181     }
182     if (!MeasureDestroy)
183       state.ResumeTiming();
184   }
185 }
186 
187 template <class Length>
188 struct StringCopy {
runStringCopy189   static void run(benchmark::State& state) { StringCopyAndDestroy<Length, true, false>(state); }
190 
nameStringCopy191   static std::string name() { return "BM_StringCopy" + Length::name(); }
192 };
193 
194 template <class Length>
195 struct StringDestroy {
runStringDestroy196   static void run(benchmark::State& state) { StringCopyAndDestroy<Length, false, true>(state); }
197 
nameStringDestroy198   static std::string name() { return "BM_StringDestroy" + Length::name(); }
199 };
200 
201 template <class Length>
202 struct StringMove {
runStringMove203   static void run(benchmark::State& state) {
204     // Keep two object locations and move construct back and forth.
205     std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2];
206     using S  = std::string;
207     size_t I = 0;
208     S* newS  = new (static_cast<void*>(Storage)) std::string(makeString(Length()));
209     for (auto _ : state) {
210       // Switch locations.
211       I ^= 1;
212       benchmark::DoNotOptimize(Storage);
213       // Move construct into the new location,
214       S* tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS));
215       // then destroy the old one.
216       newS->~S();
217       newS = tmpS;
218     }
219     newS->~S();
220   }
221 
nameStringMove222   static std::string name() { return "BM_StringMove" + Length::name(); }
223 };
224 
225 template <class Length, class Opaque>
226 struct StringResizeDefaultInit {
runStringResizeDefaultInit227   static void run(benchmark::State& state) {
228     constexpr bool opaque     = Opaque{} == Opacity::Opaque;
229     constexpr int kNumStrings = 4 << 10;
230     size_t length             = makeString(Length()).size();
231     std::string strings[kNumStrings];
232     while (state.KeepRunningBatch(kNumStrings)) {
233       state.PauseTiming();
234       for (int i = 0; i < kNumStrings; ++i) {
235         std::string().swap(strings[i]);
236       }
237       benchmark::DoNotOptimize(strings);
238       state.ResumeTiming();
239       for (int i = 0; i < kNumStrings; ++i) {
240         strings[i].__resize_default_init(maybeOpaque(length, opaque));
241       }
242     }
243   }
244 
nameStringResizeDefaultInit245   static std::string name() { return "BM_StringResizeDefaultInit" + Length::name() + Opaque::name(); }
246 };
247 
248 template <class Length, class Opaque>
249 struct StringAssignStr {
runStringAssignStr250   static void run(benchmark::State& state) {
251     constexpr bool opaque     = Opaque{} == Opacity::Opaque;
252     constexpr int kNumStrings = 4 << 10;
253     std::string src           = makeString(Length());
254     std::string strings[kNumStrings];
255     while (state.KeepRunningBatch(kNumStrings)) {
256       state.PauseTiming();
257       for (int i = 0; i < kNumStrings; ++i) {
258         std::string().swap(strings[i]);
259       }
260       benchmark::DoNotOptimize(strings);
261       state.ResumeTiming();
262       for (int i = 0; i < kNumStrings; ++i) {
263         strings[i] = *maybeOpaque(&src, opaque);
264       }
265     }
266   }
267 
nameStringAssignStr268   static std::string name() { return "BM_StringAssignStr" + Length::name() + Opaque::name(); }
269 };
270 
271 template <class Length, class Opaque>
272 struct StringAssignAsciiz {
runStringAssignAsciiz273   static void run(benchmark::State& state) {
274     constexpr bool opaque     = Opaque{} == Opacity::Opaque;
275     constexpr int kNumStrings = 4 << 10;
276     std::string strings[kNumStrings];
277     while (state.KeepRunningBatch(kNumStrings)) {
278       state.PauseTiming();
279       for (int i = 0; i < kNumStrings; ++i) {
280         std::string().swap(strings[i]);
281       }
282       benchmark::DoNotOptimize(strings);
283       state.ResumeTiming();
284       for (int i = 0; i < kNumStrings; ++i) {
285         strings[i] = maybeOpaque(getString(Length()), opaque);
286       }
287     }
288   }
289 
nameStringAssignAsciiz290   static std::string name() { return "BM_StringAssignAsciiz" + Length::name() + Opaque::name(); }
291 };
292 
293 template <class Length, class Opaque>
294 struct StringEraseToEnd {
runStringEraseToEnd295   static void run(benchmark::State& state) {
296     constexpr bool opaque     = Opaque{} == Opacity::Opaque;
297     constexpr int kNumStrings = 4 << 10;
298     std::string strings[kNumStrings];
299     const int mid = makeString(Length()).size() / 2;
300     while (state.KeepRunningBatch(kNumStrings)) {
301       state.PauseTiming();
302       for (int i = 0; i < kNumStrings; ++i) {
303         strings[i] = makeString(Length());
304       }
305       benchmark::DoNotOptimize(strings);
306       state.ResumeTiming();
307       for (int i = 0; i < kNumStrings; ++i) {
308         strings[i].erase(maybeOpaque(mid, opaque), maybeOpaque(std::string::npos, opaque));
309       }
310     }
311   }
312 
nameStringEraseToEnd313   static std::string name() { return "BM_StringEraseToEnd" + Length::name() + Opaque::name(); }
314 };
315 
316 template <class Length, class Opaque>
317 struct StringEraseWithMove {
runStringEraseWithMove318   static void run(benchmark::State& state) {
319     constexpr bool opaque     = Opaque{} == Opacity::Opaque;
320     constexpr int kNumStrings = 4 << 10;
321     std::string strings[kNumStrings];
322     const int n   = makeString(Length()).size() / 2;
323     const int pos = n / 2;
324     while (state.KeepRunningBatch(kNumStrings)) {
325       state.PauseTiming();
326       for (int i = 0; i < kNumStrings; ++i) {
327         strings[i] = makeString(Length());
328       }
329       benchmark::DoNotOptimize(strings);
330       state.ResumeTiming();
331       for (int i = 0; i < kNumStrings; ++i) {
332         strings[i].erase(maybeOpaque(pos, opaque), maybeOpaque(n, opaque));
333       }
334     }
335   }
336 
nameStringEraseWithMove337   static std::string name() { return "BM_StringEraseWithMove" + Length::name() + Opaque::name(); }
338 };
339 
340 template <class Opaque>
341 struct StringAssignAsciizMix {
runStringAssignAsciizMix342   static void run(benchmark::State& state) {
343     constexpr auto O          = Opaque{};
344     constexpr auto D          = DiffType::Control;
345     constexpr int kNumStrings = 4 << 10;
346     std::string strings[kNumStrings];
347     while (state.KeepRunningBatch(kNumStrings)) {
348       state.PauseTiming();
349       for (int i = 0; i < kNumStrings; ++i) {
350         std::string().swap(strings[i]);
351       }
352       benchmark::DoNotOptimize(strings);
353       state.ResumeTiming();
354       for (int i = 0; i < kNumStrings - 7; i += 8) {
355         strings[i + 0] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
356         strings[i + 1] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
357         strings[i + 2] = maybeOpaque(getLargeString(D), O == Opacity::Opaque);
358         strings[i + 3] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
359         strings[i + 4] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
360         strings[i + 5] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
361         strings[i + 6] = maybeOpaque(getLargeString(D), O == Opacity::Opaque);
362         strings[i + 7] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
363       }
364     }
365   }
366 
nameStringAssignAsciizMix367   static std::string name() { return "BM_StringAssignAsciizMix" + Opaque::name(); }
368 };
369 
370 enum class Relation { Eq, Less, Compare };
371 struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> {
372   static constexpr const char* Names[] = {"Eq", "Less", "Compare"};
373 };
374 
375 template <class Rel, class LHLength, class RHLength, class DiffType>
376 struct StringRelational {
runStringRelational377   static void run(benchmark::State& state) {
378     auto Lhs = makeString(RHLength());
379     auto Rhs = makeString(LHLength(), DiffType());
380     for (auto _ : state) {
381       benchmark::DoNotOptimize(Lhs);
382       benchmark::DoNotOptimize(Rhs);
383       switch (Rel()) {
384       case Relation::Eq:
385         benchmark::DoNotOptimize(Lhs == Rhs);
386         break;
387       case Relation::Less:
388         benchmark::DoNotOptimize(Lhs < Rhs);
389         break;
390       case Relation::Compare:
391         benchmark::DoNotOptimize(Lhs.compare(Rhs));
392         break;
393       }
394     }
395   }
396 
skipStringRelational397   static bool skip() {
398     // Eq is commutative, so skip half the matrix.
399     if (Rel() == Relation::Eq && LHLength() > RHLength())
400       return true;
401     // We only care about control when the lengths differ.
402     if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
403       return true;
404     // For empty, only control matters.
405     if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control)
406       return true;
407     return false;
408   }
409 
nameStringRelational410   static std::string name() {
411     return "BM_StringRelational" + Rel::name() + LHLength::name() + RHLength::name() + DiffType::name();
412   }
413 };
414 
415 template <class Rel, class LHLength, class RHLength, class DiffType>
416 struct StringRelationalLiteral {
runStringRelationalLiteral417   static void run(benchmark::State& state) {
418     auto Lhs = makeString(LHLength(), DiffType());
419     for (auto _ : state) {
420       benchmark::DoNotOptimize(Lhs);
421       constexpr const char* Literal =
422           RHLength::value == Length::Empty ? ""
423           : RHLength::value == Length::Small
424               ? SmallStringLiteral
425               : LargeStringLiteral;
426       switch (Rel()) {
427       case Relation::Eq:
428         benchmark::DoNotOptimize(Lhs == Literal);
429         break;
430       case Relation::Less:
431         benchmark::DoNotOptimize(Lhs < Literal);
432         break;
433       case Relation::Compare:
434         benchmark::DoNotOptimize(Lhs.compare(Literal));
435         break;
436       }
437     }
438   }
439 
skipStringRelationalLiteral440   static bool skip() {
441     // Doesn't matter how they differ if they have different size.
442     if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
443       return true;
444     // We don't need huge. Doensn't give anything different than Large.
445     if (LHLength() == Length::Huge || RHLength() == Length::Huge)
446       return true;
447     return false;
448   }
449 
nameStringRelationalLiteral450   static std::string name() {
451     return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() + RHLength::name() + DiffType::name();
452   }
453 };
454 
455 enum class Depth { Shallow, Deep };
456 struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> {
457   static constexpr const char* Names[] = {"Shallow", "Deep"};
458 };
459 
460 enum class Temperature { Hot, Cold };
461 struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> {
462   static constexpr const char* Names[] = {"Hot", "Cold"};
463 };
464 
465 template <class Temperature, class Depth, class Length>
466 struct StringRead {
runStringRead467   void run(benchmark::State& state) const {
468     static constexpr size_t NumStrings =
469         Temperature() == ::Temperature::Hot ? 1 << 10 : /* Enough strings to overflow the cache */ 1 << 20;
470     static_assert((NumStrings & (NumStrings - 1)) == 0, "NumStrings should be a power of two to reduce overhead.");
471 
472     std::vector<std::string> Values(NumStrings, makeString(Length()));
473     size_t I = 0;
474     for (auto _ : state) {
475       // Jump long enough to defeat cache locality, and use a value that is
476       // coprime with NumStrings to ensure we visit every element.
477       I             = (I + 17) % NumStrings;
478       const auto& V = Values[I];
479 
480       // Read everything first. Escaping data() through DoNotOptimize might
481       // cause the compiler to have to recalculate information about `V` due to
482       // aliasing.
483       const char* const Data = V.data();
484       const size_t Size      = V.size();
485       benchmark::DoNotOptimize(Data);
486       benchmark::DoNotOptimize(Size);
487       if (Depth() == ::Depth::Deep) {
488         // Read into the payload. This mainly shows the benefit of SSO when the
489         // data is cold.
490         benchmark::DoNotOptimize(*Data);
491       }
492     }
493   }
494 
skipStringRead495   static bool skip() {
496     // Huge does not give us anything that Large doesn't have. Skip it.
497     if (Length() == ::Length::Huge) {
498       return true;
499     }
500     return false;
501   }
502 
nameStringRead503   std::string name() const { return "BM_StringRead" + Temperature::name() + Depth::name() + Length::name(); }
504 };
505 
sanityCheckGeneratedStrings()506 void sanityCheckGeneratedStrings() {
507   for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
508     const auto LhsString = makeString(Lhs);
509     for (auto Rhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
510       if (Lhs > Rhs)
511         continue;
512       const auto RhsString = makeString(Rhs);
513 
514       // The smaller one must be a prefix of the larger one.
515       if (RhsString.find(LhsString) != 0) {
516         fprintf(
517             stderr, "Invalid autogenerated strings for sizes (%d,%d).\n", static_cast<int>(Lhs), static_cast<int>(Rhs));
518         std::abort();
519       }
520     }
521   }
522   // Verify the autogenerated diffs
523   for (auto L : {Length::Small, Length::Large, Length::Huge}) {
524     const auto Control = makeString(L);
525     const auto Verify  = [&](std::string Exp, size_t Pos) {
526       // Only change on the Pos char.
527       if (Control[Pos] != Exp[Pos]) {
528         Exp[Pos] = Control[Pos];
529         if (Control == Exp)
530           return;
531       }
532       fprintf(stderr, "Invalid autogenerated diff with size %d\n", static_cast<int>(L));
533       std::abort();
534     };
535     Verify(makeString(L, DiffType::ChangeFirst), 0);
536     Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2);
537     Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1);
538   }
539 }
540 
541 // Some small codegen thunks to easily see generated code.
StringEqString(const std::string & a,const std::string & b)542 bool StringEqString(const std::string& a, const std::string& b) { return a == b; }
StringEqCStr(const std::string & a,const char * b)543 bool StringEqCStr(const std::string& a, const char* b) { return a == b; }
CStrEqString(const char * a,const std::string & b)544 bool CStrEqString(const char* a, const std::string& b) { return a == b; }
StringEqCStrLiteralEmpty(const std::string & a)545 bool StringEqCStrLiteralEmpty(const std::string& a) { return a == ""; }
StringEqCStrLiteralSmall(const std::string & a)546 bool StringEqCStrLiteralSmall(const std::string& a) { return a == SmallStringLiteral; }
StringEqCStrLiteralLarge(const std::string & a)547 bool StringEqCStrLiteralLarge(const std::string& a) { return a == LargeStringLiteral; }
548 
main(int argc,char ** argv)549 int main(int argc, char** argv) {
550   benchmark::Initialize(&argc, argv);
551   if (benchmark::ReportUnrecognizedArguments(argc, argv))
552     return 1;
553 
554   sanityCheckGeneratedStrings();
555 
556   makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths, AllOpacity>();
557 
558   makeCartesianProductBenchmark<StringAssignStr, AllLengths, AllOpacity>();
559   makeCartesianProductBenchmark<StringAssignAsciiz, AllLengths, AllOpacity>();
560   makeCartesianProductBenchmark<StringAssignAsciizMix, AllOpacity>();
561 
562   makeCartesianProductBenchmark<StringCopy, AllLengths>();
563   makeCartesianProductBenchmark<StringMove, AllLengths>();
564   makeCartesianProductBenchmark<StringDestroy, AllLengths>();
565   makeCartesianProductBenchmark<StringResizeDefaultInit, AllLengths, AllOpacity>();
566   makeCartesianProductBenchmark<StringEraseToEnd, AllLengths, AllOpacity>();
567   makeCartesianProductBenchmark<StringEraseWithMove, AllLengths, AllOpacity>();
568   makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths, AllLengths, AllDiffTypes>();
569   makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations, AllLengths, AllLengths, AllDiffTypes>();
570   makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths, AllLengths>();
571   benchmark::RunSpecifiedBenchmarks();
572 
573   if (argc < 0) {
574     // ODR-use the functions to force them being generated in the binary.
575     auto functions = std::make_tuple(
576         StringEqString,
577         StringEqCStr,
578         CStrEqString,
579         StringEqCStrLiteralEmpty,
580         StringEqCStrLiteralSmall,
581         StringEqCStrLiteralLarge);
582     printf("%p", &functions);
583   }
584 }
585