summaryrefslogtreecommitdiff
path: root/benchmarks
diff options
context:
space:
mode:
authorSamuel Benzaquen <sbenza@google.com>2018-10-30 15:54:22 +0000
committerSamuel Benzaquen <sbenza@google.com>2018-10-30 15:54:22 +0000
commit3b92bcdcb74e89065bf1d386414c825462574c93 (patch)
tree4661bb87dfb5bfa858ba20ad77b212dacdd6f98d /benchmarks
parent94ce26b4105dddd5fe41c856b7f270766035218a (diff)
downloadlibcxx-3b92bcdcb74e89065bf1d386414c825462574c93.tar.gz
Add more benchmarks for std::string.
Summary: Added benchmarks for Construct, Copy, Move, Destroy, Relationals and Read. On the ones that matter, the benchmarks tests hot and cold data, and opaque and transparent inputs. Reviewers: EricWF Subscribers: christof, ldionne, libcxx-commits Differential Revision: https://reviews.llvm.org/D53825 git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@345611 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'benchmarks')
-rw-r--r--benchmarks/string.bench.cpp347
1 files changed, 326 insertions, 21 deletions
diff --git a/benchmarks/string.bench.cpp b/benchmarks/string.bench.cpp
index 8a09e738d..d7f919b14 100644
--- a/benchmarks/string.bench.cpp
+++ b/benchmarks/string.bench.cpp
@@ -1,9 +1,12 @@
-#include <unordered_set>
-#include <vector>
+
#include <cstdint>
+#include <new>
+#include <vector>
-#include "benchmark/benchmark.h"
+#include "CartesianBenchmarks.hpp"
#include "GenerateInput.hpp"
+#include "benchmark/benchmark.h"
+#include "test_macros.h"
constexpr std::size_t MAX_STRING_LEN = 8 << 14;
@@ -11,7 +14,7 @@ constexpr std::size_t MAX_STRING_LEN = 8 << 14;
static void BM_StringFindNoMatch(benchmark::State &state) {
std::string s1(state.range(0), '-');
std::string s2(8, '*');
- while (state.KeepRunning())
+ for (auto _ : state)
benchmark::DoNotOptimize(s1.find(s2));
}
BENCHMARK(BM_StringFindNoMatch)->Range(10, MAX_STRING_LEN);
@@ -20,7 +23,7 @@ BENCHMARK(BM_StringFindNoMatch)->Range(10, MAX_STRING_LEN);
static void BM_StringFindAllMatch(benchmark::State &state) {
std::string s1(MAX_STRING_LEN, '-');
std::string s2(state.range(0), '-');
- while (state.KeepRunning())
+ for (auto _ : state)
benchmark::DoNotOptimize(s1.find(s2));
}
BENCHMARK(BM_StringFindAllMatch)->Range(1, MAX_STRING_LEN);
@@ -30,7 +33,7 @@ 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), '-');
- while (state.KeepRunning())
+ for (auto _ : state)
benchmark::DoNotOptimize(s1.find(s2));
}
BENCHMARK(BM_StringFindMatch1)->Range(1, MAX_STRING_LEN / 4);
@@ -41,30 +44,332 @@ static void BM_StringFindMatch2(benchmark::State &state) {
s1 += std::string(state.range(0), '-');
s1 += std::string(state.range(0), '*');
std::string s2(state.range(0), '-');
- while (state.KeepRunning())
+ for (auto _ : state)
benchmark::DoNotOptimize(s1.find(s2));
}
BENCHMARK(BM_StringFindMatch2)->Range(1, MAX_STRING_LEN / 4);
static void BM_StringCtorDefault(benchmark::State &state) {
- while (state.KeepRunning()) {
- for (unsigned I=0; I < 1000; ++I) {
- std::string Default;
- benchmark::DoNotOptimize(Default.c_str());
- }
+ for (auto _ : state) {
+ std::string Default;
+ benchmark::DoNotOptimize(Default);
}
}
BENCHMARK(BM_StringCtorDefault);
-static void BM_StringCtorCStr(benchmark::State &state) {
- std::string Input = getRandomString(state.range(0));
- const char *Str = Input.c_str();
- benchmark::DoNotOptimize(Str);
- while (state.KeepRunning()) {
- std::string Tmp(Str);
- benchmark::DoNotOptimize(Tmp.c_str());
+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"};
+};
+
+TEST_ALWAYS_INLINE const char* getSmallString(DiffType D) {
+ switch (D) {
+ case DiffType::Control:
+ return "0123456";
+ case DiffType::ChangeFirst:
+ return "-123456";
+ case DiffType::ChangeMiddle:
+ return "012-456";
+ case DiffType::ChangeLast:
+ return "012345-";
+ }
+}
+
+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 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)>::type Storage[2];
+ using S = std::string;
+ S* Data = reinterpret_cast<S*>(Storage);
+ size_t I = 0;
+ new (static_cast<void*>(Data)) std::string(makeString(Length()));
+ for (auto _ : state) {
+ // Switch locations.
+ I ^= 1;
+ benchmark::DoNotOptimize(Storage);
+ // Move construct into the new location,
+ new (static_cast<void*>(Storage + I)) S(std::move(Data[I ^ 1]));
+ // then destroy the old one.
+ Data[I ^ 1].~S();
+ }
+ Data[I].~S();
+ }
+
+ static std::string name() { return "BM_StringMove" + Length::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();
+ }
+};
+
+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);
}
}
-BENCHMARK(BM_StringCtorCStr)->Arg(1)->Arg(8)->Range(16, MAX_STRING_LEN / 4);
-BENCHMARK_MAIN();
+int main(int argc, char** argv) {
+ benchmark::Initialize(&argc, argv);
+ if (benchmark::ReportUnrecognizedArguments(argc, argv))
+ return 1;
+
+ sanityCheckGeneratedStrings();
+
+ makeCartesianProductBenchmark<StringConstructDestroyCStr, AllLengths,
+ AllOpacity>();
+ makeCartesianProductBenchmark<StringCopy, AllLengths>();
+ makeCartesianProductBenchmark<StringMove, AllLengths>();
+ makeCartesianProductBenchmark<StringDestroy, AllLengths>();
+ makeCartesianProductBenchmark<StringRelational, AllRelations, AllLengths,
+ AllLengths, AllDiffTypes>();
+ makeCartesianProductBenchmark<StringRead, AllTemperatures, AllDepths,
+ AllLengths>();
+ benchmark::RunSpecifiedBenchmarks();
+}