Home | History | Annotate | Line # | Download | only in benchmarks
      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.
     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 SmallStringLiteral[] = "012345678";
     77 
     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 
     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 
    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 
    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 
    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 {
    158   static void run(benchmark::State& state) {
    159     for (auto _ : state) {
    160       benchmark::DoNotOptimize(
    161           makeString(Length(), DiffType::Control, Opaque()));
    162     }
    163   }
    164 
    165   static std::string name() {
    166     return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name();
    167   }
    168 };
    169 
    170 template <class Length, bool MeasureCopy, bool MeasureDestroy>
    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 {
    197   static void run(benchmark::State& state) {
    198     StringCopyAndDestroy<Length, true, false>(state);
    199   }
    200 
    201   static std::string name() { return "BM_StringCopy" + Length::name(); }
    202 };
    203 
    204 template <class Length>
    205 struct StringDestroy {
    206   static void run(benchmark::State& state) {
    207     StringCopyAndDestroy<Length, false, true>(state);
    208   }
    209 
    210   static std::string name() { return "BM_StringDestroy" + Length::name(); }
    211 };
    212 
    213 template <class Length>
    214 struct StringMove {
    215   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 
    234   static std::string name() { return "BM_StringMove" + Length::name(); }
    235 };
    236 
    237 template <class Length, class Opaque>
    238 struct StringResizeDefaultInit {
    239   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 
    257   static std::string name() {
    258     return "BM_StringResizeDefaultInit" + Length::name() + Opaque::name();
    259   }
    260 };
    261 
    262 template <class Length, class Opaque>
    263 struct StringAssignStr {
    264   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 
    282   static std::string name() {
    283     return "BM_StringAssignStr" + Length::name() + Opaque::name();
    284   }
    285 };
    286 
    287 template <class Length, class Opaque>
    288 struct StringAssignAsciiz {
    289   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 
    306   static std::string name() {
    307     return "BM_StringAssignAsciiz" + Length::name() + Opaque::name();
    308   }
    309 };
    310 
    311 template <class Length, class Opaque>
    312 struct StringEraseToEnd {
    313   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 
    332   static std::string name() {
    333     return "BM_StringEraseToEnd" + Length::name() + Opaque::name();
    334   }
    335 };
    336 
    337 template <class Length, class Opaque>
    338 struct StringEraseWithMove {
    339   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 
    358   static std::string name() {
    359     return "BM_StringEraseWithMove" + Length::name() + Opaque::name();
    360   }
    361 };
    362 
    363 template <class Opaque>
    364 struct StringAssignAsciizMix {
    365   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 
    390   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 {
    402   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 
    422   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 
    435   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 {
    443   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 
    466   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 
    476   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 {
    494   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 
    525   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 
    533   std::string name() const {
    534     return "BM_StringRead" + Temperature::name() + Depth::name() +
    535            Length::name();
    536   }
    537 };
    538 
    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.
    577 bool StringEqString(const std::string& a, const std::string& b) {
    578   return a == b;
    579 }
    580 bool StringEqCStr(const std::string& a, const char* b) { return a == b; }
    581 bool CStrEqString(const char* a, const std::string& b) { return a == b; }
    582 bool StringEqCStrLiteralEmpty(const std::string& a) {
    583   return a == "";
    584 }
    585 bool StringEqCStrLiteralSmall(const std::string& a) {
    586   return a == SmallStringLiteral;
    587 }
    588 bool StringEqCStrLiteralLarge(const std::string& a) {
    589   return a == LargeStringLiteral;
    590 }
    591 
    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