[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