629 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			629 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			C++
		
	
	
	
| 
 | |
| #include <cstdint>
 | |
| #include <new>
 | |
| #include <vector>
 | |
| 
 | |
| #include "CartesianBenchmarks.h"
 | |
| #include "GenerateInput.h"
 | |
| #include "benchmark/benchmark.h"
 | |
| #include "test_macros.h"
 | |
| 
 | |
| constexpr std::size_t MAX_STRING_LEN = 8 << 14;
 | |
| 
 | |
| // Benchmark when there is no match.
 | |
| static void BM_StringFindNoMatch(benchmark::State &state) {
 | |
|   std::string s1(state.range(0), '-');
 | |
|   std::string s2(8, '*');
 | |
|   for (auto _ : state)
 | |
|     benchmark::DoNotOptimize(s1.find(s2));
 | |
| }
 | |
| BENCHMARK(BM_StringFindNoMatch)->Range(10, MAX_STRING_LEN);
 | |
| 
 | |
| // Benchmark when the string matches first time.
 | |
| static void BM_StringFindAllMatch(benchmark::State &state) {
 | |
|   std::string s1(MAX_STRING_LEN, '-');
 | |
|   std::string s2(state.range(0), '-');
 | |
|   for (auto _ : state)
 | |
|     benchmark::DoNotOptimize(s1.find(s2));
 | |
| }
 | |
| BENCHMARK(BM_StringFindAllMatch)->Range(1, MAX_STRING_LEN);
 | |
| 
 | |
| // Benchmark when the string matches somewhere in the end.
 | |
| static void BM_StringFindMatch1(benchmark::State &state) {
 | |
|   std::string s1(MAX_STRING_LEN / 2, '*');
 | |
|   s1 += std::string(state.range(0), '-');
 | |
|   std::string s2(state.range(0), '-');
 | |
|   for (auto _ : state)
 | |
|     benchmark::DoNotOptimize(s1.find(s2));
 | |
| }
 | |
| BENCHMARK(BM_StringFindMatch1)->Range(1, MAX_STRING_LEN / 4);
 | |
| 
 | |
| // Benchmark when the string matches somewhere from middle to the end.
 | |
| static void BM_StringFindMatch2(benchmark::State &state) {
 | |
|   std::string s1(MAX_STRING_LEN / 2, '*');
 | |
|   s1 += std::string(state.range(0), '-');
 | |
|   s1 += std::string(state.range(0), '*');
 | |
|   std::string s2(state.range(0), '-');
 | |
|   for (auto _ : state)
 | |
|     benchmark::DoNotOptimize(s1.find(s2));
 | |
| }
 | |
| BENCHMARK(BM_StringFindMatch2)->Range(1, MAX_STRING_LEN / 4);
 | |
| 
 | |
| static void BM_StringCtorDefault(benchmark::State &state) {
 | |
|   for (auto _ : state) {
 | |
|     std::string Default;
 | |
|     benchmark::DoNotOptimize(Default);
 | |
|   }
 | |
| }
 | |
| BENCHMARK(BM_StringCtorDefault);
 | |
| 
 | |
| enum class Length { Empty, Small, Large, Huge };
 | |
| struct AllLengths : EnumValuesAsTuple<AllLengths, Length, 4> {
 | |
|   static constexpr const char* Names[] = {"Empty", "Small", "Large", "Huge"};
 | |
| };
 | |
| 
 | |
| enum class Opacity { Opaque, Transparent };
 | |
| struct AllOpacity : EnumValuesAsTuple<AllOpacity, Opacity, 2> {
 | |
|   static constexpr const char* Names[] = {"Opaque", "Transparent"};
 | |
| };
 | |
| 
 | |
| enum class DiffType { Control, ChangeFirst, ChangeMiddle, ChangeLast };
 | |
| struct AllDiffTypes : EnumValuesAsTuple<AllDiffTypes, DiffType, 4> {
 | |
|   static constexpr const char* Names[] = {"Control", "ChangeFirst",
 | |
|                                           "ChangeMiddle", "ChangeLast"};
 | |
| };
 | |
| 
 | |
| static constexpr char SmallStringLiteral[] = "012345678";
 | |
| 
 | |
| TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
 | |
|   switch (D) {
 | |
|     case DiffType::Control:
 | |
|       return SmallStringLiteral;
 | |
|     case DiffType::ChangeFirst:
 | |
|       return "-12345678";
 | |
|     case DiffType::ChangeMiddle:
 | |
|       return "0123-5678";
 | |
|     case DiffType::ChangeLast:
 | |
|       return "01234567-";
 | |
|   }
 | |
| }
 | |
| 
 | |
| static constexpr char LargeStringLiteral[] =
 | |
|     "012345678901234567890123456789012345678901234567890123456789012";
 | |
| 
 | |
| TEST_ALWAYS_INLINE const char* getLargeString(DiffType D) {
 | |
| #define LARGE_STRING_FIRST "123456789012345678901234567890"
 | |
| #define LARGE_STRING_SECOND "234567890123456789012345678901"
 | |
|   switch (D) {
 | |
|     case DiffType::Control:
 | |
|       return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
 | |
|     case DiffType::ChangeFirst:
 | |
|       return "-" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "2";
 | |
|     case DiffType::ChangeMiddle:
 | |
|       return "0" LARGE_STRING_FIRST "-" LARGE_STRING_SECOND "2";
 | |
|     case DiffType::ChangeLast:
 | |
|       return "0" LARGE_STRING_FIRST "1" LARGE_STRING_SECOND "-";
 | |
|   }
 | |
| }
 | |
| 
 | |
| TEST_ALWAYS_INLINE const char* getHugeString(DiffType D) {
 | |
| #define HUGE_STRING0 "0123456789"
 | |
| #define HUGE_STRING1 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0 HUGE_STRING0
 | |
| #define HUGE_STRING2 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1 HUGE_STRING1
 | |
| #define HUGE_STRING3 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2 HUGE_STRING2
 | |
| #define HUGE_STRING4 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3 HUGE_STRING3
 | |
|   switch (D) {
 | |
|     case DiffType::Control:
 | |
|       return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
 | |
|     case DiffType::ChangeFirst:
 | |
|       return "-123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "0123456789";
 | |
|     case DiffType::ChangeMiddle:
 | |
|       return "0123456789" HUGE_STRING4 "01234-6789" HUGE_STRING4 "0123456789";
 | |
|     case DiffType::ChangeLast:
 | |
|       return "0123456789" HUGE_STRING4 "0123456789" HUGE_STRING4 "012345678-";
 | |
|   }
 | |
| }
 | |
| 
 | |
| TEST_ALWAYS_INLINE const char* getString(Length L,
 | |
|                                          DiffType D = DiffType::Control) {
 | |
|   switch (L) {
 | |
|   case Length::Empty:
 | |
|     return "";
 | |
|   case Length::Small:
 | |
|     return getSmallString(D);
 | |
|   case Length::Large:
 | |
|     return getLargeString(D);
 | |
|   case Length::Huge:
 | |
|     return getHugeString(D);
 | |
|   }
 | |
| }
 | |
| 
 | |
| TEST_ALWAYS_INLINE std::string makeString(Length L,
 | |
|                                           DiffType D = DiffType::Control,
 | |
|                                           Opacity O = Opacity::Transparent) {
 | |
|   switch (L) {
 | |
|   case Length::Empty:
 | |
|     return maybeOpaque("", O == Opacity::Opaque);
 | |
|   case Length::Small:
 | |
|     return maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|   case Length::Large:
 | |
|     return maybeOpaque(getLargeString(D), O == Opacity::Opaque);
 | |
|   case Length::Huge:
 | |
|     return maybeOpaque(getHugeString(D), O == Opacity::Opaque);
 | |
|   }
 | |
| }
 | |
| 
 | |
| template <class Length, class Opaque>
 | |
| struct StringConstructDestroyCStr {
 | |
|   static void run(benchmark::State& state) {
 | |
|     for (auto _ : state) {
 | |
|       benchmark::DoNotOptimize(
 | |
|           makeString(Length(), DiffType::Control, Opaque()));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringConstructDestroyCStr" + Length::name() + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Length, bool MeasureCopy, bool MeasureDestroy>
 | |
| static void StringCopyAndDestroy(benchmark::State& state) {
 | |
|   static constexpr size_t NumStrings = 1024;
 | |
|   auto Orig = makeString(Length());
 | |
|   std::aligned_storage<sizeof(std::string)>::type Storage[NumStrings];
 | |
| 
 | |
|   while (state.KeepRunningBatch(NumStrings)) {
 | |
|     if (!MeasureCopy)
 | |
|       state.PauseTiming();
 | |
|     for (size_t I = 0; I < NumStrings; ++I) {
 | |
|       ::new (static_cast<void*>(Storage + I)) std::string(Orig);
 | |
|     }
 | |
|     if (!MeasureCopy)
 | |
|       state.ResumeTiming();
 | |
|     if (!MeasureDestroy)
 | |
|       state.PauseTiming();
 | |
|     for (size_t I = 0; I < NumStrings; ++I) {
 | |
|       using S = std::string;
 | |
|       reinterpret_cast<S*>(Storage + I)->~S();
 | |
|     }
 | |
|     if (!MeasureDestroy)
 | |
|       state.ResumeTiming();
 | |
|   }
 | |
| }
 | |
| 
 | |
| template <class Length>
 | |
| struct StringCopy {
 | |
|   static void run(benchmark::State& state) {
 | |
|     StringCopyAndDestroy<Length, true, false>(state);
 | |
|   }
 | |
| 
 | |
|   static std::string name() { return "BM_StringCopy" + Length::name(); }
 | |
| };
 | |
| 
 | |
| template <class Length>
 | |
| struct StringDestroy {
 | |
|   static void run(benchmark::State& state) {
 | |
|     StringCopyAndDestroy<Length, false, true>(state);
 | |
|   }
 | |
| 
 | |
|   static std::string name() { return "BM_StringDestroy" + Length::name(); }
 | |
| };
 | |
| 
 | |
| template <class Length>
 | |
| struct StringMove {
 | |
|   static void run(benchmark::State& state) {
 | |
|     // Keep two object locations and move construct back and forth.
 | |
|     std::aligned_storage<sizeof(std::string), alignof(std::string)>::type Storage[2];
 | |
|     using S = std::string;
 | |
|     size_t I = 0;
 | |
|     S *newS = new (static_cast<void*>(Storage)) std::string(makeString(Length()));
 | |
|     for (auto _ : state) {
 | |
|       // Switch locations.
 | |
|       I ^= 1;
 | |
|       benchmark::DoNotOptimize(Storage);
 | |
|       // Move construct into the new location,
 | |
|       S *tmpS = new (static_cast<void*>(Storage + I)) S(std::move(*newS));
 | |
|       // then destroy the old one.
 | |
|       newS->~S();
 | |
|       newS = tmpS;
 | |
|     }
 | |
|     newS->~S();
 | |
|   }
 | |
| 
 | |
|   static std::string name() { return "BM_StringMove" + Length::name(); }
 | |
| };
 | |
| 
 | |
| template <class Length, class Opaque>
 | |
| struct StringResizeDefaultInit {
 | |
|   static void run(benchmark::State& state) {
 | |
|     constexpr bool opaque = Opaque{} == Opacity::Opaque;
 | |
|     constexpr int kNumStrings = 4 << 10;
 | |
|     size_t length = makeString(Length()).size();
 | |
|     std::string strings[kNumStrings];
 | |
|     while (state.KeepRunningBatch(kNumStrings)) {
 | |
|       state.PauseTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         std::string().swap(strings[i]);
 | |
|       }
 | |
|       benchmark::DoNotOptimize(strings);
 | |
|       state.ResumeTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i].__resize_default_init(maybeOpaque(length, opaque));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringResizeDefaultInit" + Length::name() + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Length, class Opaque>
 | |
| struct StringAssignStr {
 | |
|   static void run(benchmark::State& state) {
 | |
|     constexpr bool opaque = Opaque{} == Opacity::Opaque;
 | |
|     constexpr int kNumStrings = 4 << 10;
 | |
|     std::string src = makeString(Length());
 | |
|     std::string strings[kNumStrings];
 | |
|     while (state.KeepRunningBatch(kNumStrings)) {
 | |
|       state.PauseTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         std::string().swap(strings[i]);
 | |
|       }
 | |
|       benchmark::DoNotOptimize(strings);
 | |
|       state.ResumeTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i] = *maybeOpaque(&src, opaque);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringAssignStr" + Length::name() + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Length, class Opaque>
 | |
| struct StringAssignAsciiz {
 | |
|   static void run(benchmark::State& state) {
 | |
|     constexpr bool opaque = Opaque{} == Opacity::Opaque;
 | |
|     constexpr int kNumStrings = 4 << 10;
 | |
|     std::string strings[kNumStrings];
 | |
|     while (state.KeepRunningBatch(kNumStrings)) {
 | |
|       state.PauseTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         std::string().swap(strings[i]);
 | |
|       }
 | |
|       benchmark::DoNotOptimize(strings);
 | |
|       state.ResumeTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i] = maybeOpaque(getString(Length()), opaque);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringAssignAsciiz" + Length::name() + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Length, class Opaque>
 | |
| struct StringEraseToEnd {
 | |
|   static void run(benchmark::State& state) {
 | |
|     constexpr bool opaque = Opaque{} == Opacity::Opaque;
 | |
|     constexpr int kNumStrings = 4 << 10;
 | |
|     std::string strings[kNumStrings];
 | |
|     const int mid = makeString(Length()).size() / 2;
 | |
|     while (state.KeepRunningBatch(kNumStrings)) {
 | |
|       state.PauseTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i] = makeString(Length());
 | |
|       }
 | |
|       benchmark::DoNotOptimize(strings);
 | |
|       state.ResumeTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i].erase(maybeOpaque(mid, opaque),
 | |
|                          maybeOpaque(std::string::npos, opaque));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringEraseToEnd" + Length::name() + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Length, class Opaque>
 | |
| struct StringEraseWithMove {
 | |
|   static void run(benchmark::State& state) {
 | |
|     constexpr bool opaque = Opaque{} == Opacity::Opaque;
 | |
|     constexpr int kNumStrings = 4 << 10;
 | |
|     std::string strings[kNumStrings];
 | |
|     const int n = makeString(Length()).size() / 2;
 | |
|     const int pos = n / 2;
 | |
|     while (state.KeepRunningBatch(kNumStrings)) {
 | |
|       state.PauseTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i] = makeString(Length());
 | |
|       }
 | |
|       benchmark::DoNotOptimize(strings);
 | |
|       state.ResumeTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         strings[i].erase(maybeOpaque(pos, opaque), maybeOpaque(n, opaque));
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringEraseWithMove" + Length::name() + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Opaque>
 | |
| struct StringAssignAsciizMix {
 | |
|   static void run(benchmark::State& state) {
 | |
|     constexpr auto O = Opaque{};
 | |
|     constexpr auto D = DiffType::Control;
 | |
|     constexpr int kNumStrings = 4 << 10;
 | |
|     std::string strings[kNumStrings];
 | |
|     while (state.KeepRunningBatch(kNumStrings)) {
 | |
|       state.PauseTiming();
 | |
|       for (int i = 0; i < kNumStrings; ++i) {
 | |
|         std::string().swap(strings[i]);
 | |
|       }
 | |
|       benchmark::DoNotOptimize(strings);
 | |
|       state.ResumeTiming();
 | |
|       for (int i = 0; i < kNumStrings - 7; i += 8) {
 | |
|         strings[i + 0] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|         strings[i + 1] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|         strings[i + 2] = maybeOpaque(getLargeString(D), O == Opacity::Opaque);
 | |
|         strings[i + 3] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|         strings[i + 4] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|         strings[i + 5] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|         strings[i + 6] = maybeOpaque(getLargeString(D), O == Opacity::Opaque);
 | |
|         strings[i + 7] = maybeOpaque(getSmallString(D), O == Opacity::Opaque);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringAssignAsciizMix" + Opaque::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| enum class Relation { Eq, Less, Compare };
 | |
| struct AllRelations : EnumValuesAsTuple<AllRelations, Relation, 3> {
 | |
|   static constexpr const char* Names[] = {"Eq", "Less", "Compare"};
 | |
| };
 | |
| 
 | |
| template <class Rel, class LHLength, class RHLength, class DiffType>
 | |
| struct StringRelational {
 | |
|   static void run(benchmark::State& state) {
 | |
|     auto Lhs = makeString(RHLength());
 | |
|     auto Rhs = makeString(LHLength(), DiffType());
 | |
|     for (auto _ : state) {
 | |
|       benchmark::DoNotOptimize(Lhs);
 | |
|       benchmark::DoNotOptimize(Rhs);
 | |
|       switch (Rel()) {
 | |
|       case Relation::Eq:
 | |
|         benchmark::DoNotOptimize(Lhs == Rhs);
 | |
|         break;
 | |
|       case Relation::Less:
 | |
|         benchmark::DoNotOptimize(Lhs < Rhs);
 | |
|         break;
 | |
|       case Relation::Compare:
 | |
|         benchmark::DoNotOptimize(Lhs.compare(Rhs));
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static bool skip() {
 | |
|     // Eq is commutative, so skip half the matrix.
 | |
|     if (Rel() == Relation::Eq && LHLength() > RHLength())
 | |
|       return true;
 | |
|     // We only care about control when the lengths differ.
 | |
|     if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
 | |
|       return true;
 | |
|     // For empty, only control matters.
 | |
|     if (LHLength() == Length::Empty && DiffType() != ::DiffType::Control)
 | |
|       return true;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringRelational" + Rel::name() + LHLength::name() +
 | |
|            RHLength::name() + DiffType::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| template <class Rel, class LHLength, class RHLength, class DiffType>
 | |
| struct StringRelationalLiteral {
 | |
|   static void run(benchmark::State& state) {
 | |
|     auto Lhs = makeString(LHLength(), DiffType());
 | |
|     for (auto _ : state) {
 | |
|       benchmark::DoNotOptimize(Lhs);
 | |
|       constexpr const char* Literal = RHLength::value == Length::Empty
 | |
|                                           ? ""
 | |
|                                           : RHLength::value == Length::Small
 | |
|                                                 ? SmallStringLiteral
 | |
|                                                 : LargeStringLiteral;
 | |
|       switch (Rel()) {
 | |
|       case Relation::Eq:
 | |
|         benchmark::DoNotOptimize(Lhs == Literal);
 | |
|         break;
 | |
|       case Relation::Less:
 | |
|         benchmark::DoNotOptimize(Lhs < Literal);
 | |
|         break;
 | |
|       case Relation::Compare:
 | |
|         benchmark::DoNotOptimize(Lhs.compare(Literal));
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static bool skip() {
 | |
|     // Doesn't matter how they differ if they have different size.
 | |
|     if (LHLength() != RHLength() && DiffType() != ::DiffType::Control)
 | |
|       return true;
 | |
|     // We don't need huge. Doensn't give anything different than Large.
 | |
|     if (LHLength() == Length::Huge || RHLength() == Length::Huge)
 | |
|       return true;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   static std::string name() {
 | |
|     return "BM_StringRelationalLiteral" + Rel::name() + LHLength::name() +
 | |
|            RHLength::name() + DiffType::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| enum class Depth { Shallow, Deep };
 | |
| struct AllDepths : EnumValuesAsTuple<AllDepths, Depth, 2> {
 | |
|   static constexpr const char* Names[] = {"Shallow", "Deep"};
 | |
| };
 | |
| 
 | |
| enum class Temperature { Hot, Cold };
 | |
| struct AllTemperatures : EnumValuesAsTuple<AllTemperatures, Temperature, 2> {
 | |
|   static constexpr const char* Names[] = {"Hot", "Cold"};
 | |
| };
 | |
| 
 | |
| template <class Temperature, class Depth, class Length>
 | |
| struct StringRead {
 | |
|   void run(benchmark::State& state) const {
 | |
|     static constexpr size_t NumStrings =
 | |
|         Temperature() == ::Temperature::Hot
 | |
|             ? 1 << 10
 | |
|             : /* Enough strings to overflow the cache */ 1 << 20;
 | |
|     static_assert((NumStrings & (NumStrings - 1)) == 0,
 | |
|                   "NumStrings should be a power of two to reduce overhead.");
 | |
| 
 | |
|     std::vector<std::string> Values(NumStrings, makeString(Length()));
 | |
|     size_t I = 0;
 | |
|     for (auto _ : state) {
 | |
|       // Jump long enough to defeat cache locality, and use a value that is
 | |
|       // coprime with NumStrings to ensure we visit every element.
 | |
|       I = (I + 17) % NumStrings;
 | |
|       const auto& V = Values[I];
 | |
| 
 | |
|       // Read everything first. Escaping data() through DoNotOptimize might
 | |
|       // cause the compiler to have to recalculate information about `V` due to
 | |
|       // aliasing.
 | |
|       const char* const Data = V.data();
 | |
|       const size_t Size = V.size();
 | |
|       benchmark::DoNotOptimize(Data);
 | |
|       benchmark::DoNotOptimize(Size);
 | |
|       if (Depth() == ::Depth::Deep) {
 | |
|         // Read into the payload. This mainly shows the benefit of SSO when the
 | |
|         // data is cold.
 | |
|         benchmark::DoNotOptimize(*Data);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static bool skip() {
 | |
|     // Huge does not give us anything that Large doesn't have. Skip it.
 | |
|     if (Length() == ::Length::Huge) {
 | |
|       return true;
 | |
|     }
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   std::string name() const {
 | |
|     return "BM_StringRead" + Temperature::name() + Depth::name() +
 | |
|            Length::name();
 | |
|   }
 | |
| };
 | |
| 
 | |
| void sanityCheckGeneratedStrings() {
 | |
|   for (auto Lhs : {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
 | |
|     const auto LhsString = makeString(Lhs);
 | |
|     for (auto Rhs :
 | |
|          {Length::Empty, Length::Small, Length::Large, Length::Huge}) {
 | |
|       if (Lhs > Rhs)
 | |
|         continue;
 | |
|       const auto RhsString = makeString(Rhs);
 | |
| 
 | |
|       // The smaller one must be a prefix of the larger one.
 | |
|       if (RhsString.find(LhsString) != 0) {
 | |
|         fprintf(stderr, "Invalid autogenerated strings for sizes (%d,%d).\n",
 | |
|                 static_cast<int>(Lhs), static_cast<int>(Rhs));
 | |
|         std::abort();
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   // Verify the autogenerated diffs
 | |
|   for (auto L : {Length::Small, Length::Large, Length::Huge}) {
 | |
|     const auto Control = makeString(L);
 | |
|     const auto Verify = [&](std::string Exp, size_t Pos) {
 | |
|       // Only change on the Pos char.
 | |
|       if (Control[Pos] != Exp[Pos]) {
 | |
|         Exp[Pos] = Control[Pos];
 | |
|         if (Control == Exp)
 | |
|           return;
 | |
|       }
 | |
|       fprintf(stderr, "Invalid autogenerated diff with size %d\n",
 | |
|               static_cast<int>(L));
 | |
|       std::abort();
 | |
|     };
 | |
|     Verify(makeString(L, DiffType::ChangeFirst), 0);
 | |
|     Verify(makeString(L, DiffType::ChangeMiddle), Control.size() / 2);
 | |
|     Verify(makeString(L, DiffType::ChangeLast), Control.size() - 1);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Some small codegen thunks to easily see generated code.
 | |
| bool StringEqString(const std::string& a, const std::string& b) {
 | |
|   return a == b;
 | |
| }
 | |
| bool StringEqCStr(const std::string& a, const char* b) { return a == b; }
 | |
| bool CStrEqString(const char* a, const std::string& b) { return a == b; }
 | |
| bool StringEqCStrLiteralEmpty(const std::string& a) {
 | |
|   return a == "";
 | |
| }
 | |
| bool StringEqCStrLiteralSmall(const std::string& a) {
 | |
|   return a == SmallStringLiteral;
 | |
| }
 | |
| bool StringEqCStrLiteralLarge(const std::string& a) {
 | |
|   return a == LargeStringLiteral;
 | |
| }
 | |
| 
 | |
| int main(int argc, char** argv) {
 | |
|   benchmark::Initialize(&argc, argv);
 | |
|   if (benchmark::ReportUnrecognizedArguments(argc, argv))
 | |
|     return 1;
 | |
| 
 | |
|   sanityCheckGeneratedStrings();
 | |
| 
 | |
|   makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths,
 | |
|                                 AllOpacity>();
 | |
| 
 | |
|   makeCartesianProductBenchmark<StringAssignStr, AllLengths, AllOpacity>();
 | |
|   makeCartesianProductBenchmark<StringAssignAsciiz, AllLengths, AllOpacity>();
 | |
|   makeCartesianProductBenchmark<StringAssignAsciizMix, AllOpacity>();
 | |
| 
 | |
|   makeCartesianProductBenchmark<StringCopy, AllLengths>();
 | |
|   makeCartesianProductBenchmark<StringMove, AllLengths>();
 | |
|   makeCartesianProductBenchmark<StringDestroy, AllLengths>();
 | |
|   makeCartesianProductBenchmark<StringResizeDefaultInit, AllLengths,
 | |
|                                 AllOpacity>();
 | |
|   makeCartesianProductBenchmark<StringEraseToEnd, AllLengths, AllOpacity>();
 | |
|   makeCartesianProductBenchmark<StringEraseWithMove, AllLengths, AllOpacity>();
 | |
|   makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths,
 | |
|                                 AllLengths, AllDiffTypes>();
 | |
|   makeCartesianProductBenchmark<StringRelationalLiteral, AllRelations,
 | |
|                                 AllLengths, AllLengths, AllDiffTypes>();
 | |
|   makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths,
 | |
|                                 AllLengths>();
 | |
|   benchmark::RunSpecifiedBenchmarks();
 | |
| 
 | |
|   if (argc < 0) {
 | |
|     // ODR-use the functions to force them being generated in the binary.
 | |
|     auto functions = std::make_tuple(
 | |
|         StringEqString, StringEqCStr, CStrEqString, StringEqCStrLiteralEmpty,
 | |
|         StringEqCStrLiteralSmall, StringEqCStrLiteralLarge);
 | |
|     printf("%p", &functions);
 | |
|   }
 | |
| }
 |