#include #include #include #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 { static constexpr const char* Names[] = {"Empty", "Small", "Large", "Huge"}; }; enum class Opacity { Opaque, Transparent }; struct AllOpacity : EnumValuesAsTuple { static constexpr const char* Names[] = {"Opaque", "Transparent"}; }; enum class DiffType { Control, ChangeFirst, ChangeMiddle, ChangeLast }; struct AllDiffTypes : EnumValuesAsTuple { 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 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 static void StringCopyAndDestroy(benchmark::State& state) { static constexpr size_t NumStrings = 1024; auto Orig = makeString(Length()); std::aligned_storage::type Storage[NumStrings]; while (state.KeepRunningBatch(NumStrings)) { if (!MeasureCopy) state.PauseTiming(); for (size_t I = 0; I < NumStrings; ++I) { ::new (static_cast(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(Storage + I)->~S(); } if (!MeasureDestroy) state.ResumeTiming(); } } template struct StringCopy { static void run(benchmark::State& state) { StringCopyAndDestroy(state); } static std::string name() { return "BM_StringCopy" + Length::name(); } }; template struct StringDestroy { static void run(benchmark::State& state) { StringCopyAndDestroy(state); } static std::string name() { return "BM_StringDestroy" + Length::name(); } }; template struct StringMove { static void run(benchmark::State& state) { // Keep two object locations and move construct back and forth. std::aligned_storage::type Storage[2]; using S = std::string; size_t I = 0; S *newS = new (static_cast(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(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 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 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 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 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 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 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 { static constexpr const char* Names[] = {"Eq", "Less", "Compare"}; }; template 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 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 { static constexpr const char* Names[] = {"Shallow", "Deep"}; }; enum class Temperature { Hot, Cold }; struct AllTemperatures : EnumValuesAsTuple { static constexpr const char* Names[] = {"Hot", "Cold"}; }; template 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 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(Lhs), static_cast(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(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(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); makeCartesianProductBenchmark(); 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); } }