[libcxx-commits] [libcxx] [libc++] Refactor the string benchmarks (PR #185397)
Nikolas Klauser via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Mar 9 04:44:17 PDT 2026
https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/185397
Fixes #179696
>From f1674bb77875f4351317fa333352762901801d63 Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Thu, 12 Feb 2026 15:08:00 +0100
Subject: [PATCH] [libc++] Refactor the string benchmarks
---
.../benchmarks/containers/string.bench.cpp | 1013 ++++++++---------
1 file changed, 449 insertions(+), 564 deletions(-)
diff --git a/libcxx/test/benchmarks/containers/string.bench.cpp b/libcxx/test/benchmarks/containers/string.bench.cpp
index 98216d22d0144..74c061abe01d3 100644
--- a/libcxx/test/benchmarks/containers/string.bench.cpp
+++ b/libcxx/test/benchmarks/containers/string.bench.cpp
@@ -6,602 +6,487 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: c++03, c++11, c++14, c++17
#include <algorithm>
-#include <cstdint>
-#include <cstdlib>
-#include <new>
+#include <array>
+#include <functional>
+#include <string>
#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_StringFindStringLiteral(benchmark::State& state) {
- std::string s;
-
- for (int i = 0; i < state.range(0); i++)
- s += 'a';
-
- s += 'b';
-
- benchmark::DoNotOptimize(s.data());
- benchmark::ClobberMemory();
- size_t pos;
-
- for (auto _ : state) {
- benchmark::DoNotOptimize(pos = s.find("b"));
- benchmark::ClobberMemory();
+#include "make_string.h"
+
+std::string rename(std::string str, std::string_view replacement) {
+ while (true) {
+ auto pos = str.find("basic_string");
+ if (pos == std::string::npos)
+ return str;
+ str.replace(pos, std::strlen("basic_string"), replacement);
}
}
-BENCHMARK(BM_StringFindStringLiteral)->RangeMultiplier(2)->Range(8, 8 << 10);
-
-static void BM_StringFindCharLiteral(benchmark::State& state) {
- std::string s;
-
- for (int i = 0; i < state.range(0); i++)
- s += 'a';
-
- s += 'b';
-
- benchmark::DoNotOptimize(s.data());
- benchmark::ClobberMemory();
- size_t pos;
-
- for (auto _ : state) {
- benchmark::DoNotOptimize(pos = s.find('b'));
- benchmark::ClobberMemory();
- }
-}
-BENCHMARK(BM_StringFindCharLiteral)->RangeMultiplier(2)->Range(8, 8 << 10);
-
-static void BM_StringCtorDefault(benchmark::State& state) {
- for (auto _ : state) {
- std::string Default;
- benchmark::DoNotOptimize(Default);
- }
-}
-BENCHMARK(BM_StringCtorDefault);
-
-static void BM_StringResizeAndOverwrite(benchmark::State& state) {
- std::string str;
-
- for (auto _ : state) {
- benchmark::DoNotOptimize(str);
- str.resize_and_overwrite(10, [](char* ptr, size_t n) {
- std::fill_n(ptr, n, 'a');
- return n;
- });
- benchmark::DoNotOptimize(str);
- str.clear();
- }
-}
-BENCHMARK(BM_StringResizeAndOverwrite);
-
-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-";
- }
- __builtin_unreachable();
-}
-
-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 "-";
- }
- __builtin_unreachable();
-}
-
-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-";
- }
- __builtin_unreachable();
-}
-
-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);
- }
- __builtin_unreachable();
-}
-
-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);
- }
- __builtin_unreachable();
-}
-
-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 Func, class Mod = decltype([](auto) {})>
+void bench(std::string name, Func func, Mod modifier = {}) {
+ benchmark::RegisterBenchmark(rename(name, "string"), [=](benchmark::State& state) {
+ func(std::type_identity<char>(), state);
+ })->Apply(modifier);
-template <class Length, bool MeasureCopy, bool MeasureDestroy>
-static void StringCopyAndDestroy(benchmark::State& state) {
- static constexpr size_t NumStrings = 1024;
- auto Orig = makeString(Length());
- alignas(std::string) char Storage[NumStrings * sizeof(std::string)];
+ benchmark::RegisterBenchmark(rename(name, "u8string"), [=](benchmark::State& state) {
+ func(std::type_identity<char8_t>(), state);
+ })->Apply(modifier);
- while (state.KeepRunningBatch(NumStrings)) {
- if (!MeasureCopy)
- state.PauseTiming();
- for (size_t I = 0; I < NumStrings; ++I) {
- ::new (reinterpret_cast<std::string*>(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();
- }
+ benchmark::RegisterBenchmark(rename(name, "wstring"), [=](benchmark::State& state) {
+ func(std::type_identity<wchar_t>(), state);
+ })->Apply(modifier);
}
-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.
- alignas(std::string) char Storage[2 * sizeof(std::string)];
- using S = std::string;
- size_t I = 0;
- S* newS = new (reinterpret_cast<std::string*>(Storage)) std::string(makeString(Length()));
+int main(int argc, char** argv) {
+ // [string.cons]
+ bench("std::basic_string::basic_string()", []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
for (auto _ : state) {
- // Switch locations.
- I ^= 1;
- benchmark::DoNotOptimize(Storage);
- // Move construct into the new location,
- S* tmpS = new (reinterpret_cast<std::string*>(Storage) + I) S(std::move(*newS));
- // then destroy the old one.
- newS->~S();
- newS = tmpS;
+ std::basic_string<CharT> str;
+ benchmark::DoNotOptimize(str);
}
- newS->~S();
+ });
+
+ bench("std::basic_string::basic_string(const value_type*)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> str(state.range(), 'a');
+
+ for (auto _ : state) {
+ auto ptr = str.data();
+ std::basic_string<CharT> copy(ptr);
+ benchmark::DoNotOptimize(copy);
+ }
+ });
+
+ bench(
+ "std::basic_string::basic_string(const basic_string&)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> str(state.range(), 'a');
+
+ for (auto _ : state) {
+ auto copy = str;
+ benchmark::DoNotOptimize(copy);
+ }
+ },
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench(
+ "std::basic_string::basic_string(basic_string&&)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+
+ union U {
+ U() {}
+ ~U() {}
+
+ struct {
+ str s1;
+ str s2;
+ };
+ } u;
+
+ std::construct_at(&u.s1, state.range(), 'a');
+
+ for (auto _ : state) {
+ std::construct_at(&u.s2, std::move(u.s1));
+ std::destroy_at(&u.s1);
+ benchmark::DoNotOptimize(u);
+
+ std::construct_at(&u.s1, std::move(u.s2));
+ std::destroy_at(&u.s2);
+ benchmark::DoNotOptimize(u);
+ }
+
+ std::destroy_at(&u.s1);
+ },
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ {
+ static auto bench_impl =
+ []<bool opaque, class CharT>(std::bool_constant<opaque>, std::type_identity<CharT>, benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+ str src(state.range(), 'a');
+
+ str strings[4096];
+ while (state.KeepRunningBatch(std::size(strings))) {
+ state.PauseTiming();
+ for (auto& string : strings)
+ str().swap(string); // Make sure the strings are in the default constructed state
+ state.ResumeTiming();
+ benchmark::DoNotOptimize(strings);
+
+ for (auto& string : strings) {
+ if constexpr (opaque)
+ benchmark::DoNotOptimize(src);
+ string = src;
+ }
+ }
+ };
+
+ bench("std::basic_string::operator=(const std::basic_string&) (opaque)",
+ std::bind_front(bench_impl, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::operator=(const std::basic_string&) (transparent)",
+ std::bind_front(bench_impl, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- static std::string name() { return "BM_StringMove" + Length::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 auto bench_impl =
+ []<size_t size, bool opaque, class CharT>(
+ std::integral_constant<size_t, size>,
+ std::bool_constant<opaque>,
+ std::type_identity<CharT>,
+ benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+
+ static constexpr std::array<CharT, size> src = [] {
+ std::array<CharT, size> ret;
+ std::ranges::fill(ret, 'a');
+ ret[size - 1] = '\0';
+ return ret;
+ }();
+
+ str strings[4096];
+ while (state.KeepRunningBatch(std::size(strings))) {
+ state.PauseTiming();
+ for (auto& string : strings)
+ str().swap(string); // Make sure the strings are in the default constructed state
+ state.ResumeTiming();
+ benchmark::DoNotOptimize(strings);
+
+ for (auto& string : strings) {
+ auto ptr = src.data();
+ if constexpr (opaque)
+ benchmark::DoNotOptimize(ptr);
+ string = ptr;
+ }
+ }
+ };
+
+ bench("std::basic_string::operator=(const value_type*) (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::operator=(const value_type*) (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::operator=(const value_type*) (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::operator=(const value_type*) (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- 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());
+ // [string.capacity]
+ bench("std::basic_string::size()", []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> str;
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;
- }
+ benchmark::DoNotOptimize(str);
+ benchmark::DoNotOptimize(str.size());
}
+ });
+
+ bench("std::basic_string::resize_and_overwrite()",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> str;
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(str);
+ str.resize_and_overwrite(10, [](CharT* ptr, size_t n) {
+ std::fill_n(ptr, n, 'a');
+ return n;
+ });
+ benchmark::DoNotOptimize(str);
+ str.clear();
+ }
+ });
+
+ // [string.modifiers]
+
+ {
+ static auto bench_impl =
+ []<size_t end, bool opaque, class CharT>(
+ std::integral_constant<size_t, end>,
+ std::bool_constant<opaque>,
+ std::type_identity<CharT>,
+ benchmark::State& state) {
+ std::basic_string<CharT> strings[4096];
+
+ size_t size = state.range();
+ size_t pos = size / 2;
+ auto npos = end;
+ while (state.KeepRunningBatch(std::size(strings))) {
+ state.PauseTiming();
+ for (auto& string : strings)
+ string.resize(size, 'a');
+ state.ResumeTiming();
+ for (auto& string : strings) {
+ if constexpr (opaque) {
+ benchmark::DoNotOptimize(pos);
+ benchmark::DoNotOptimize(npos);
+ }
+ string.erase(pos, npos);
+ }
+ }
+ };
+ bench("std::basic_string::erase() (to end of string, opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, std::string::npos>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+ bench("std::basic_string::erase() (to end of string, transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, std::string::npos>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+ bench("std::basic_string::erase() (in the middle, opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 2>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+ bench("std::basic_string::erase() (in the middle, transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 2>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- 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());
+ // [string.ops]
+ bench("std::basic_string::data()", []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> str;
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;
- }
+ benchmark::DoNotOptimize(str);
+ benchmark::DoNotOptimize(str.data());
}
+ });
+
+ auto search_sizes = [](auto bm) { bm->Arg(5)->Arg(30)->Arg(8192); };
+ bench(
+ "std::basic_string::find(std::basic_string) (no match)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> haystack(state.range(), 'a');
+ std::basic_string<CharT> needle(8, 'b');
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(haystack.find(needle));
+ }
+ },
+ search_sizes);
+
+ bench(
+ "std::basic_string::find(std::basic_string) (match at the end)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> haystack(state.range(), 'a');
+ std::basic_string<CharT> needle(8, 'b');
+ haystack += needle;
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(haystack.find(needle));
+ }
+ },
+ search_sizes);
+
+ bench(
+ "std::basic_string::find(const value_type*) (literal)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> s1(state.range(), 'a');
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(s1.find(MAKE_CSTRING(CharT, "b")));
+ }
+ },
+ search_sizes);
+
+ bench(
+ "std::basic_string::find(value_type) (literal)",
+ []<class CharT>(std::type_identity<CharT>, benchmark::State& state) {
+ std::basic_string<CharT> s1(state.range(), 'a');
+
+ for (auto _ : state) {
+ benchmark::DoNotOptimize(s1.find('b'));
+ }
+ },
+ search_sizes);
+
+ // [string.cmp]
+ {
+ static auto bench_impl =
+ []<size_t size, bool opaque, class CharT>(
+ std::integral_constant<size_t, size>,
+ std::bool_constant<opaque>,
+ std::type_identity<CharT>,
+ benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+
+ static constexpr std::array<CharT, size> src = [] {
+ std::array<CharT, size> ret;
+ std::ranges::fill(ret, 'a');
+ ret[size - 1] = '\0';
+ return ret;
+ }();
+
+ str strings[4096];
+ std::fill_n(strings, 4096, src.data());
+ while (state.KeepRunningBatch(std::size(strings))) {
+ benchmark::DoNotOptimize(strings);
+
+ for (auto& string : strings) {
+ auto ptr = src.data();
+ if constexpr (opaque)
+ benchmark::DoNotOptimize(ptr);
+ benchmark::DoNotOptimize(string == ptr);
+ }
+ }
+ };
+
+ bench("std::basic_string == const CharT* (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string == const CharT* (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string == const CharT* (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string == const CharT* (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- 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;
- 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.
- char* Data = V.data();
- 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 auto bench_impl =
+ []<size_t size, bool opaque, class CharT>(
+ std::integral_constant<size_t, size>,
+ std::bool_constant<opaque>,
+ std::type_identity<CharT>,
+ benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+
+ static constexpr std::array<CharT, size> src = [] {
+ std::array<CharT, size> ret;
+ std::ranges::fill(ret, 'a');
+ ret[size - 1] = '\0';
+ return ret;
+ }();
+
+ str strings[4096];
+ std::fill_n(strings, 4096, src.data());
+ while (state.KeepRunningBatch(std::size(strings))) {
+ benchmark::DoNotOptimize(strings);
+
+ for (auto& string : strings) {
+ auto ptr = src.data();
+ if constexpr (opaque)
+ benchmark::DoNotOptimize(ptr);
+ benchmark::DoNotOptimize(string.compare(ptr));
+ }
+ }
+ };
+
+ bench("std::basic_string::compare(const CharT*) (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::compare(const CharT*) (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::compare(const CharT*) (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::compare(const CharT*) (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- static bool skip() {
- // Huge does not give us anything that Large doesn't have. Skip it.
- return Length() == ::Length::Huge;
+ // [string.compare]
+ {
+ static auto bench_impl =
+ []<size_t size, bool opaque, class CharT>(
+ std::integral_constant<size_t, size>,
+ std::bool_constant<opaque>,
+ std::type_identity<CharT>,
+ benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+
+ std::basic_string<CharT> src(size, 'a');
+
+ str strings[4096];
+ std::fill_n(strings, 4096, src.data());
+ while (state.KeepRunningBatch(std::size(strings))) {
+ benchmark::DoNotOptimize(strings);
+
+ for (auto& string : strings) {
+ if constexpr (opaque)
+ benchmark::DoNotOptimize(src);
+ benchmark::DoNotOptimize(string == src);
+ }
+ }
+ };
+
+ bench("std::basic_string == std::basic_string (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string == std::basic_string (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string == std::basic_string (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string == std::basic_string (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- 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();
- }
- }
+ {
+ static auto bench_impl =
+ []<size_t size, bool opaque, class CharT>(
+ std::integral_constant<size_t, size>,
+ std::bool_constant<opaque>,
+ std::type_identity<CharT>,
+ benchmark::State& state) {
+ using str = std::basic_string<CharT>;
+
+ std::basic_string<CharT> src(size, 'a');
+
+ str strings[4096];
+ std::fill_n(strings, 4096, src.data());
+ while (state.KeepRunningBatch(std::size(strings))) {
+ benchmark::DoNotOptimize(strings);
+
+ for (auto& string : strings) {
+ if constexpr (opaque)
+ benchmark::DoNotOptimize(src);
+ benchmark::DoNotOptimize(string.compare(src));
+ }
+ }
+ };
+
+ bench("std::basic_string::compare(const CharT*) (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::compare(const CharT*) (opaque)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::true_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::compare(const CharT*) (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 5>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
+
+ bench("std::basic_string::compare(const CharT*) (transparent)",
+ std::bind_front(bench_impl, std::integral_constant<size_t, 30>{}, std::false_type{}),
+ [](auto bm) { bm->Arg(5)->Arg(30); });
}
- // 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);
- }
-}
-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<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();
+ benchmark::Shutdown();
+ return 0;
}
More information about the libcxx-commits
mailing list