[libcxx-commits] [libcxx] [libc++] Further refactor sequence container benchmarks (PR #126129)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Thu Feb 6 12:55:44 PST 2025


https://github.com/ldionne created https://github.com/llvm/llvm-project/pull/126129

This patch does not significantly change how the sequence container benchmarks are done, but it adopts the same style as the associative container benchmarks.

This commit does adjust how we were benchmarking push_back, where we never really measured the overhead of the slow path of push_back (when we need to reallocate).

>From 4f07c4973e0efce792f5215b4a83bd8315c87f73 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 6 Feb 2025 13:12:40 -0500
Subject: [PATCH] [libc++] Further refactor sequence container benchmarks

This patch does not significantly change how the sequence container
benchmarks are done, but it adopts the same style as the associative
container benchmarks.

However, this commit does adjust how we were benchmarking push_back,
where we never really measured the overhead of the slow path of
push_back (when we need to reallocate).
---
 .../containers/container_benchmarks.h         | 609 ------------------
 .../containers/{ => sequence}/deque.bench.cpp |   6 +-
 .../containers/{ => sequence}/list.bench.cpp  |   6 +-
 .../sequence/sequence_container_benchmarks.h  | 456 +++++++++++++
 .../{ => sequence}/vector.bench.cpp           |   6 +-
 5 files changed, 465 insertions(+), 618 deletions(-)
 delete mode 100644 libcxx/test/benchmarks/containers/container_benchmarks.h
 rename libcxx/test/benchmarks/containers/{ => sequence}/deque.bench.cpp (73%)
 rename libcxx/test/benchmarks/containers/{ => sequence}/list.bench.cpp (73%)
 create mode 100644 libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
 rename libcxx/test/benchmarks/containers/{ => sequence}/vector.bench.cpp (73%)

diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
deleted file mode 100644
index e24bd767177e874..000000000000000
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ /dev/null
@@ -1,609 +0,0 @@
-// -*- C++ -*-
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef TEST_BENCHMARKS_CONTAINERS_CONTAINER_BENCHMARKS_H
-#define TEST_BENCHMARKS_CONTAINERS_CONTAINER_BENCHMARKS_H
-
-#include <algorithm>
-#include <cstddef>
-#include <iterator>
-#include <ranges> // for std::from_range
-#include <string>
-#include <type_traits>
-#include <vector>
-
-#include "benchmark/benchmark.h"
-#include "test_iterators.h"
-#include "test_macros.h"
-#include "../GenerateInput.h"
-
-namespace ContainerBenchmarks {
-
-template <class Container>
-void DoNotOptimizeData(Container& c) {
-  if constexpr (requires { c.data(); }) {
-    benchmark::DoNotOptimize(c.data());
-  } else {
-    benchmark::DoNotOptimize(&c);
-  }
-}
-
-//
-// Sequence container operations
-//
-template <class Container>
-void BM_ctor_size(benchmark::State& st) {
-  auto size = st.range(0);
-
-  for (auto _ : st) {
-    Container c(size); // we assume the destructor doesn't dominate the benchmark
-    DoNotOptimizeData(c);
-  }
-}
-
-template <class Container, class Generator>
-void BM_ctor_size_value(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const auto size = st.range(0);
-  ValueType value = gen();
-  benchmark::DoNotOptimize(value);
-
-  for (auto _ : st) {
-    Container c(size, value); // we assume the destructor doesn't dominate the benchmark
-    DoNotOptimizeData(c);
-  }
-}
-
-template <class Container, class Generator>
-void BM_ctor_iter_iter(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const auto size = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  const auto begin = in.begin();
-  const auto end   = in.end();
-  benchmark::DoNotOptimize(in);
-
-  for (auto _ : st) {
-    Container c(begin, end); // we assume the destructor doesn't dominate the benchmark
-    DoNotOptimizeData(c);
-  }
-}
-
-#if TEST_STD_VER >= 23
-template <class Container, class Generator>
-void BM_ctor_from_range(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const auto size = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  benchmark::DoNotOptimize(in);
-
-  for (auto _ : st) {
-    Container c(std::from_range, in); // we assume the destructor doesn't dominate the benchmark
-    DoNotOptimizeData(c);
-  }
-}
-#endif
-
-template <class Container, class Generator>
-void BM_ctor_copy(benchmark::State& st, Generator gen) {
-  auto size = st.range(0);
-  Container in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  for (auto _ : st) {
-    Container c(in); // we assume the destructor doesn't dominate the benchmark
-    DoNotOptimizeData(c);
-    DoNotOptimizeData(in);
-  }
-}
-
-template <class Container, class Generator>
-void BM_assignment(benchmark::State& st, Generator gen) {
-  auto size = st.range(0);
-  Container in1, in2;
-  std::generate_n(std::back_inserter(in1), size, gen);
-  std::generate_n(std::back_inserter(in2), size, gen);
-  DoNotOptimizeData(in1);
-  DoNotOptimizeData(in2);
-
-  // Assign from one of two containers in succession to avoid
-  // hitting a self-assignment corner-case
-  Container c(in1);
-  bool toggle = false;
-  for (auto _ : st) {
-    c      = toggle ? in1 : in2;
-    toggle = !toggle;
-    DoNotOptimizeData(c);
-    DoNotOptimizeData(in1);
-    DoNotOptimizeData(in2);
-  }
-}
-
-// Benchmark Container::assign(input-iter, input-iter) when the container already contains
-// the same number of elements that we're assigning. The intent is to check whether the
-// implementation basically creates a new container from scratch or manages to reuse the
-// pre-existing storage.
-template <typename Container, class Generator>
-void BM_assign_input_iter_full(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  auto size       = st.range(0);
-  std::vector<ValueType> in1, in2;
-  std::generate_n(std::back_inserter(in1), size, gen);
-  std::generate_n(std::back_inserter(in2), size, gen);
-  DoNotOptimizeData(in1);
-  DoNotOptimizeData(in2);
-
-  Container c(in1.begin(), in1.end());
-  bool toggle = false;
-  for (auto _ : st) {
-    std::vector<ValueType>& in = toggle ? in1 : in2;
-    auto first                 = in.data();
-    auto last                  = in.data() + in.size();
-    c.assign(cpp17_input_iterator(first), cpp17_input_iterator(last));
-    toggle = !toggle;
-    DoNotOptimizeData(c);
-  }
-}
-
-template <class Container, class Generator>
-void BM_insert_begin(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  Container c(in.begin(), in.end());
-  DoNotOptimizeData(c);
-
-  ValueType value = gen();
-  benchmark::DoNotOptimize(value);
-
-  for (auto _ : st) {
-    c.insert(c.begin(), value);
-    DoNotOptimizeData(c);
-
-    c.erase(std::prev(c.end())); // avoid growing indefinitely
-  }
-}
-
-template <class Container, class Generator>
-  requires std::random_access_iterator<typename Container::iterator>
-void BM_insert_middle(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  Container c(in.begin(), in.end());
-  DoNotOptimizeData(c);
-
-  ValueType value = gen();
-  benchmark::DoNotOptimize(value);
-
-  for (auto _ : st) {
-    auto mid = c.begin() + (size / 2); // requires random-access iterators in order to make sense
-    c.insert(mid, value);
-    DoNotOptimizeData(c);
-
-    c.erase(c.end() - 1); // avoid growing indefinitely
-  }
-}
-
-// Insert at the start of a vector in a scenario where the vector already
-// has enough capacity to hold all the elements we are inserting.
-template <class Container, class Generator>
-void BM_insert_begin_input_iter_with_reserve_no_realloc(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-  auto first = in.data();
-  auto last  = in.data() + in.size();
-
-  const int small = 100; // arbitrary
-  Container c;
-  c.reserve(size + small); // ensure no reallocation
-  std::generate_n(std::back_inserter(c), small, gen);
-
-  for (auto _ : st) {
-    c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
-    DoNotOptimizeData(c);
-
-    st.PauseTiming();
-    c.erase(c.begin() + small, c.end()); // avoid growing indefinitely
-    st.ResumeTiming();
-  }
-}
-
-// Insert at the start of a vector in a scenario where the vector already
-// has almost enough capacity to hold all the elements we are inserting,
-// but does need to reallocate.
-template <class Container, class Generator>
-void BM_insert_begin_input_iter_with_reserve_almost_no_realloc(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-  auto first = in.data();
-  auto last  = in.data() + in.size();
-
-  const int overflow = size / 10; // 10% of elements won't fit in the vector when we insert
-  Container c;
-  for (auto _ : st) {
-    st.PauseTiming();
-    c = Container();
-    c.reserve(size);
-    std::generate_n(std::back_inserter(c), overflow, gen);
-    st.ResumeTiming();
-
-    c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
-    DoNotOptimizeData(c);
-  }
-}
-
-// Insert at the start of a vector in a scenario where the vector can fit a few
-// more elements, but needs to reallocate almost immediately to fit the remaining
-// elements.
-template <class Container, class Generator>
-void BM_insert_begin_input_iter_with_reserve_near_full(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-  auto first = in.data();
-  auto last  = in.data() + in.size();
-
-  const int overflow = 9 * (size / 10); // 90% of elements won't fit in the vector when we insert
-  Container c;
-  for (auto _ : st) {
-    st.PauseTiming();
-    c = Container();
-    c.reserve(size);
-    std::generate_n(std::back_inserter(c), overflow, gen);
-    st.ResumeTiming();
-
-    c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
-    DoNotOptimizeData(c);
-  }
-}
-
-template <class Container, class Generator>
-void BM_erase_begin(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  Container c(in.begin(), in.end());
-  DoNotOptimizeData(c);
-
-  ValueType value = gen();
-  benchmark::DoNotOptimize(value);
-
-  for (auto _ : st) {
-    c.erase(c.begin());
-    DoNotOptimizeData(c);
-
-    c.insert(c.end(), value); // re-insert an element at the end to avoid needing a new container
-  }
-}
-
-template <class Container, class Generator>
-  requires std::random_access_iterator<typename Container::iterator>
-void BM_erase_middle(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  Container c(in.begin(), in.end());
-  DoNotOptimizeData(c);
-
-  ValueType value = gen();
-  benchmark::DoNotOptimize(value);
-
-  for (auto _ : st) {
-    auto mid = c.begin() + (size / 2);
-    c.erase(mid);
-    DoNotOptimizeData(c);
-
-    c.insert(c.end(), value); // re-insert an element at the end to avoid needing a new container
-  }
-}
-
-template <class Container, class Generator>
-void BM_push_back(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  Container c;
-  DoNotOptimizeData(c);
-  while (st.KeepRunningBatch(size)) {
-    c.clear();
-    for (int i = 0; i != size; ++i) {
-      c.push_back(in[i]);
-    }
-    DoNotOptimizeData(c);
-  }
-}
-
-template <class Container, class Generator>
-void BM_push_back_with_reserve(benchmark::State& st, Generator gen) {
-  using ValueType = typename Container::value_type;
-  const int size  = st.range(0);
-  std::vector<ValueType> in;
-  std::generate_n(std::back_inserter(in), size, gen);
-  DoNotOptimizeData(in);
-
-  Container c;
-  c.reserve(size);
-  DoNotOptimizeData(c);
-  while (st.KeepRunningBatch(size)) {
-    c.clear();
-    for (int i = 0; i != size; ++i) {
-      c.push_back(in[i]);
-    }
-    DoNotOptimizeData(c);
-  }
-}
-
-template <class Container>
-void sequence_container_benchmarks(std::string container) {
-  using ValueType = typename Container::value_type;
-
-  using Generator     = ValueType (*)();
-  Generator cheap     = [] { return Generate<ValueType>::cheap(); };
-  Generator expensive = [] { return Generate<ValueType>::expensive(); };
-  auto tostr          = [&](Generator gen) { return gen == cheap ? " (cheap elements)" : " (expensive elements)"; };
-  std::vector<Generator> generators;
-  generators.push_back(cheap);
-  if constexpr (!std::is_integral_v<ValueType>) {
-    generators.push_back(expensive);
-  }
-
-  // constructors
-  if constexpr (std::is_constructible_v<Container, std::size_t>) {
-    // not all containers provide this one
-    benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size<Container>)->Arg(1024);
-  }
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::ctor(size, value_type)" + tostr(gen), [=](auto& st) {
-      BM_ctor_size_value<Container>(st, gen);
-    })->Arg(1024);
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator)" + tostr(gen), [=](auto& st) {
-      BM_ctor_iter_iter<Container>(st, gen);
-    })->Arg(1024);
-#if TEST_STD_VER >= 23
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::ctor(Range)" + tostr(gen), [=](auto& st) {
-      BM_ctor_from_range<Container>(st, gen);
-    })->Arg(1024);
-#endif
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::ctor(const&)" + tostr(gen), [=](auto& st) {
-      BM_ctor_copy<Container>(st, gen);
-    })->Arg(1024);
-
-  // assignment
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::operator=(const&)" + tostr(gen), [=](auto& st) {
-      BM_assignment<Container>(st, gen);
-    })->Arg(1024);
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::assign(input-iter, input-iter) (full container)" + tostr(gen),
-                                 [=](auto& st) { BM_assign_input_iter_full<Container>(st, gen); })
-        ->Arg(1024);
-
-  // insert
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::insert(begin)" + tostr(gen), [=](auto& st) {
-      BM_insert_begin<Container>(st, gen);
-    })->Arg(1024);
-  if constexpr (std::random_access_iterator<typename Container::iterator>) {
-    for (auto gen : generators)
-      benchmark::RegisterBenchmark(container + "::insert(middle)" + tostr(gen), [=](auto& st) {
-        BM_insert_middle<Container>(st, gen);
-      })->Arg(1024);
-  }
-  if constexpr (requires(Container c) { c.reserve(0); }) {
-    for (auto gen : generators)
-      benchmark::RegisterBenchmark(
-          container + "::insert(begin, input-iter, input-iter) (no realloc)" + tostr(gen),
-          [=](auto& st) { BM_insert_begin_input_iter_with_reserve_no_realloc<Container>(st, gen); })
-          ->Arg(1024);
-    for (auto gen : generators)
-      benchmark::RegisterBenchmark(
-          container + "::insert(begin, input-iter, input-iter) (half filled)" + tostr(gen),
-          [=](auto& st) { BM_insert_begin_input_iter_with_reserve_almost_no_realloc<Container>(st, gen); })
-          ->Arg(1024);
-    for (auto gen : generators)
-      benchmark::RegisterBenchmark(
-          container + "::insert(begin, input-iter, input-iter) (near full)" + tostr(gen),
-          [=](auto& st) { BM_insert_begin_input_iter_with_reserve_near_full<Container>(st, gen); })
-          ->Arg(1024);
-  }
-
-  // erase
-  for (auto gen : generators)
-    benchmark::RegisterBenchmark(container + "::erase(begin)" + tostr(gen), [=](auto& st) {
-      BM_erase_begin<Container>(st, gen);
-    })->Arg(1024);
-  if constexpr (std::random_access_iterator<typename Container::iterator>) {
-    for (auto gen : generators)
-      benchmark::RegisterBenchmark(container + "::erase(middle)" + tostr(gen), [=](auto& st) {
-        BM_erase_middle<Container>(st, gen);
-      })->Arg(1024);
-  }
-
-  // push_back (optional)
-  if constexpr (requires(Container c, ValueType v) { c.push_back(v); }) {
-    for (auto gen : generators)
-      benchmark::RegisterBenchmark(container + "::push_back()" + tostr(gen), [=](auto& st) {
-        BM_push_back<Container>(st, gen);
-      })->Arg(1024);
-    if constexpr (requires(Container c) { c.reserve(0); }) {
-      for (auto gen : generators)
-        benchmark::RegisterBenchmark(container + "::push_back() (with reserve)" + tostr(gen), [=](auto& st) {
-          BM_push_back_with_reserve<Container>(st, gen);
-        })->Arg(1024);
-    }
-  }
-}
-
-//
-// Misc operations
-//
-template <class Container, class GenInputs>
-void BM_InsertValue(benchmark::State& st, Container c, GenInputs gen) {
-  auto in        = gen(st.range(0));
-  const auto end = in.end();
-  while (st.KeepRunning()) {
-    c.clear();
-    for (auto it = in.begin(); it != end; ++it) {
-      benchmark::DoNotOptimize(&(*c.insert(*it).first));
-    }
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_InsertValueRehash(benchmark::State& st, Container c, GenInputs gen) {
-  auto in        = gen(st.range(0));
-  const auto end = in.end();
-  while (st.KeepRunning()) {
-    c.clear();
-    c.rehash(16);
-    for (auto it = in.begin(); it != end; ++it) {
-      benchmark::DoNotOptimize(&(*c.insert(*it).first));
-    }
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_InsertDuplicate(benchmark::State& st, Container c, GenInputs gen) {
-  auto in        = gen(st.range(0));
-  const auto end = in.end();
-  c.insert(in.begin(), in.end());
-  benchmark::DoNotOptimize(c);
-  benchmark::DoNotOptimize(in);
-  while (st.KeepRunning()) {
-    for (auto it = in.begin(); it != end; ++it) {
-      benchmark::DoNotOptimize(&(*c.insert(*it).first));
-    }
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_EmplaceDuplicate(benchmark::State& st, Container c, GenInputs gen) {
-  auto in        = gen(st.range(0));
-  const auto end = in.end();
-  c.insert(in.begin(), in.end());
-  benchmark::DoNotOptimize(c);
-  benchmark::DoNotOptimize(in);
-  while (st.KeepRunning()) {
-    for (auto it = in.begin(); it != end; ++it) {
-      benchmark::DoNotOptimize(&(*c.emplace(*it).first));
-    }
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_Find(benchmark::State& st, Container c, GenInputs gen) {
-  auto in = gen(st.range(0));
-  c.insert(in.begin(), in.end());
-  benchmark::DoNotOptimize(&(*c.begin()));
-  const auto end = in.data() + in.size();
-  while (st.KeepRunning()) {
-    for (auto it = in.data(); it != end; ++it) {
-      benchmark::DoNotOptimize(&(*c.find(*it)));
-    }
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_FindRehash(benchmark::State& st, Container c, GenInputs gen) {
-  c.rehash(8);
-  auto in = gen(st.range(0));
-  c.insert(in.begin(), in.end());
-  benchmark::DoNotOptimize(&(*c.begin()));
-  const auto end = in.data() + in.size();
-  while (st.KeepRunning()) {
-    for (auto it = in.data(); it != end; ++it) {
-      benchmark::DoNotOptimize(&(*c.find(*it)));
-    }
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_Rehash(benchmark::State& st, Container c, GenInputs gen) {
-  auto in = gen(st.range(0));
-  c.max_load_factor(3.0);
-  c.insert(in.begin(), in.end());
-  benchmark::DoNotOptimize(c);
-  const auto bucket_count = c.bucket_count();
-  while (st.KeepRunning()) {
-    c.rehash(bucket_count + 1);
-    c.rehash(bucket_count);
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_Compare_same_container(benchmark::State& st, Container, GenInputs gen) {
-  auto in = gen(st.range(0));
-  Container c1(in.begin(), in.end());
-  Container c2 = c1;
-
-  benchmark::DoNotOptimize(&(*c1.begin()));
-  benchmark::DoNotOptimize(&(*c2.begin()));
-  while (st.KeepRunning()) {
-    bool res = c1 == c2;
-    benchmark::DoNotOptimize(&res);
-    benchmark::ClobberMemory();
-  }
-}
-
-template <class Container, class GenInputs>
-void BM_Compare_different_containers(benchmark::State& st, Container, GenInputs gen) {
-  auto in1 = gen(st.range(0));
-  auto in2 = gen(st.range(0));
-  Container c1(in1.begin(), in1.end());
-  Container c2(in2.begin(), in2.end());
-
-  benchmark::DoNotOptimize(&(*c1.begin()));
-  benchmark::DoNotOptimize(&(*c2.begin()));
-  while (st.KeepRunning()) {
-    bool res = c1 == c2;
-    benchmark::DoNotOptimize(&res);
-    benchmark::ClobberMemory();
-  }
-}
-
-} // namespace ContainerBenchmarks
-
-#endif // TEST_BENCHMARKS_CONTAINERS_CONTAINER_BENCHMARKS_H
diff --git a/libcxx/test/benchmarks/containers/deque.bench.cpp b/libcxx/test/benchmarks/containers/sequence/deque.bench.cpp
similarity index 73%
rename from libcxx/test/benchmarks/containers/deque.bench.cpp
rename to libcxx/test/benchmarks/containers/sequence/deque.bench.cpp
index 6a650fa4dce2ac7..e37c9fef4ac23f6 100644
--- a/libcxx/test/benchmarks/containers/deque.bench.cpp
+++ b/libcxx/test/benchmarks/containers/sequence/deque.bench.cpp
@@ -11,12 +11,12 @@
 #include <deque>
 #include <string>
 
-#include "container_benchmarks.h"
+#include "sequence_container_benchmarks.h"
 #include "benchmark/benchmark.h"
 
 int main(int argc, char** argv) {
-  ContainerBenchmarks::sequence_container_benchmarks<std::deque<int>>("std::deque<int>");
-  ContainerBenchmarks::sequence_container_benchmarks<std::deque<std::string>>("std::deque<std::string>");
+  support::sequence_container_benchmarks<std::deque<int>>("std::deque<int>");
+  support::sequence_container_benchmarks<std::deque<std::string>>("std::deque<std::string>");
 
   benchmark::Initialize(&argc, argv);
   benchmark::RunSpecifiedBenchmarks();
diff --git a/libcxx/test/benchmarks/containers/list.bench.cpp b/libcxx/test/benchmarks/containers/sequence/list.bench.cpp
similarity index 73%
rename from libcxx/test/benchmarks/containers/list.bench.cpp
rename to libcxx/test/benchmarks/containers/sequence/list.bench.cpp
index 2212affa02ba4ed..e40aae6cf9fa528 100644
--- a/libcxx/test/benchmarks/containers/list.bench.cpp
+++ b/libcxx/test/benchmarks/containers/sequence/list.bench.cpp
@@ -11,12 +11,12 @@
 #include <list>
 #include <string>
 
-#include "container_benchmarks.h"
+#include "sequence_container_benchmarks.h"
 #include "benchmark/benchmark.h"
 
 int main(int argc, char** argv) {
-  ContainerBenchmarks::sequence_container_benchmarks<std::list<int>>("std::list<int>");
-  ContainerBenchmarks::sequence_container_benchmarks<std::list<std::string>>("std::list<std::string>");
+  support::sequence_container_benchmarks<std::list<int>>("std::list<int>");
+  support::sequence_container_benchmarks<std::list<std::string>>("std::list<std::string>");
 
   benchmark::Initialize(&argc, argv);
   benchmark::RunSpecifiedBenchmarks();
diff --git a/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h b/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
new file mode 100644
index 000000000000000..d844d1d93a8ce39
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
@@ -0,0 +1,456 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_BENCHMARKS_CONTAINERS_SEQUENCE_SEQUENCE_CONTAINER_BENCHMARKS_H
+#define TEST_BENCHMARKS_CONTAINERS_SEQUENCE_SEQUENCE_CONTAINER_BENCHMARKS_H
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <iterator>
+#include <ranges> // for std::from_range
+#include <string>
+#include <type_traits>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../../GenerateInput.h"
+
+namespace support {
+
+template <class Container>
+void DoNotOptimizeData(Container& c) {
+  if constexpr (requires { c.data(); }) {
+    benchmark::DoNotOptimize(c.data());
+  } else {
+    benchmark::DoNotOptimize(&c);
+  }
+}
+
+template <class Container>
+void sequence_container_benchmarks(std::string container) {
+  using ValueType = typename Container::value_type;
+
+  using Generator     = ValueType (*)();
+  Generator cheap     = [] { return Generate<ValueType>::cheap(); };
+  Generator expensive = [] { return Generate<ValueType>::expensive(); };
+  auto tostr          = [&](Generator gen) -> std::string {
+    return gen == cheap ? " (cheap elements)" : " (expensive elements)";
+  };
+  std::vector<Generator> generators;
+  generators.push_back(cheap);
+  if constexpr (!std::is_integral_v<ValueType>) {
+    generators.push_back(expensive);
+  }
+
+  // Some of these benchmarks are structured to perform the operation being benchmarked
+  // a small number of times at each iteration, in order to offset the cost of
+  // PauseTiming() and ResumeTiming().
+  static constexpr std::size_t BatchSize = 32;
+
+  auto bench = [&](std::string operation, auto f) {
+    benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(32)->Arg(1024)->Arg(8192);
+  };
+
+  /////////////////////////
+  // Constructors
+  /////////////////////////
+  if constexpr (std::is_constructible_v<Container, std::size_t>) {
+    // not all containers provide this constructor
+    bench("ctor(size)", [](auto& st) {
+      auto const size = st.range(0);
+
+      for ([[maybe_unused]] auto _ : st) {
+        Container c(size); // we assume the destructor doesn't dominate the benchmark
+        DoNotOptimizeData(c);
+      }
+    });
+  }
+
+  for (auto gen : generators)
+    bench("ctor(size, value_type)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      ValueType value = gen();
+      benchmark::DoNotOptimize(value);
+
+      for ([[maybe_unused]] auto _ : st) {
+        Container c(size, value); // we assume the destructor doesn't dominate the benchmark
+        DoNotOptimizeData(c);
+      }
+    });
+
+  for (auto gen : generators)
+    bench("ctor(Iterator, Iterator)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      std::vector<ValueType> in;
+      std::generate_n(std::back_inserter(in), size, gen);
+      const auto begin = in.begin();
+      const auto end   = in.end();
+      benchmark::DoNotOptimize(in);
+
+      for ([[maybe_unused]] auto _ : st) {
+        Container c(begin, end); // we assume the destructor doesn't dominate the benchmark
+        DoNotOptimizeData(c);
+      }
+    });
+
+#if TEST_STD_VER >= 23
+  for (auto gen : generators)
+    bench("ctor(Range)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      std::vector<ValueType> in;
+      std::generate_n(std::back_inserter(in), size, gen);
+      benchmark::DoNotOptimize(in);
+
+      for ([[maybe_unused]] auto _ : st) {
+        Container c(std::from_range, in); // we assume the destructor doesn't dominate the benchmark
+        DoNotOptimizeData(c);
+      }
+    });
+#endif
+
+  for (auto gen : generators)
+    bench("ctor(const&)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      Container in;
+      std::generate_n(std::back_inserter(in), size, gen);
+      DoNotOptimizeData(in);
+
+      for ([[maybe_unused]] auto _ : st) {
+        Container c(in); // we assume the destructor doesn't dominate the benchmark
+        DoNotOptimizeData(c);
+        DoNotOptimizeData(in);
+      }
+    });
+
+  /////////////////////////
+  // Assignment
+  /////////////////////////
+  for (auto gen : generators)
+    bench("operator=(const&)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      Container in1, in2;
+      std::generate_n(std::back_inserter(in1), size, gen);
+      std::generate_n(std::back_inserter(in2), size, gen);
+      DoNotOptimizeData(in1);
+      DoNotOptimizeData(in2);
+
+      // Assign from one of two containers in succession to avoid
+      // hitting a self-assignment corner-case
+      Container c(in1);
+      bool toggle = false;
+      for ([[maybe_unused]] auto _ : st) {
+        c      = toggle ? in1 : in2;
+        toggle = !toggle;
+        DoNotOptimizeData(c);
+        DoNotOptimizeData(in1);
+        DoNotOptimizeData(in2);
+      }
+    });
+
+  // Benchmark Container::assign(input-iter, input-iter) when the container already contains
+  // the same number of elements that we're assigning. The intent is to check whether the
+  // implementation basically creates a new container from scratch or manages to reuse the
+  // pre-existing storage.
+  for (auto gen : generators)
+    bench("assign(input-iter, input-iter) (full container)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      std::vector<ValueType> in1, in2;
+      std::generate_n(std::back_inserter(in1), size, gen);
+      std::generate_n(std::back_inserter(in2), size, gen);
+      DoNotOptimizeData(in1);
+      DoNotOptimizeData(in2);
+
+      Container c(in1.begin(), in1.end());
+      bool toggle = false;
+      for ([[maybe_unused]] auto _ : st) {
+        std::vector<ValueType>& in = toggle ? in1 : in2;
+        auto first                 = in.data();
+        auto last                  = in.data() + in.size();
+        c.assign(cpp17_input_iterator(first), cpp17_input_iterator(last));
+        toggle = !toggle;
+        DoNotOptimizeData(c);
+      }
+    });
+
+  /////////////////////////
+  // Insertion
+  /////////////////////////
+  for (auto gen : generators)
+    bench("insert(begin)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      std::vector<ValueType> in;
+      std::generate_n(std::back_inserter(in), size, gen);
+      DoNotOptimizeData(in);
+
+      Container c(in.begin(), in.end());
+      DoNotOptimizeData(c);
+
+      ValueType value = gen();
+      benchmark::DoNotOptimize(value);
+
+      for ([[maybe_unused]] auto _ : st) {
+        c.insert(c.begin(), value);
+        DoNotOptimizeData(c);
+
+        c.erase(std::prev(c.end())); // avoid growing indefinitely
+      }
+    });
+
+  if constexpr (std::random_access_iterator<typename Container::iterator>) {
+    for (auto gen : generators)
+      bench("insert(middle)" + tostr(gen), [gen](auto& st) {
+        auto const size = st.range(0);
+        std::vector<ValueType> in;
+        std::generate_n(std::back_inserter(in), size, gen);
+        DoNotOptimizeData(in);
+
+        Container c(in.begin(), in.end());
+        DoNotOptimizeData(c);
+
+        ValueType value = gen();
+        benchmark::DoNotOptimize(value);
+
+        for ([[maybe_unused]] auto _ : st) {
+          auto mid = c.begin() + (size / 2); // requires random-access iterators in order to make sense
+          c.insert(mid, value);
+          DoNotOptimizeData(c);
+
+          c.erase(c.end() - 1); // avoid growing indefinitely
+        }
+      });
+  }
+
+  if constexpr (requires(Container c) { c.reserve(0); }) {
+    // Insert at the start of a vector in a scenario where the vector already
+    // has enough capacity to hold all the elements we are inserting.
+    for (auto gen : generators)
+      bench("insert(begin, input-iter, input-iter) (no realloc)" + tostr(gen), [gen](auto& st) {
+        auto const size = st.range(0);
+        std::vector<ValueType> in;
+        std::generate_n(std::back_inserter(in), size, gen);
+        DoNotOptimizeData(in);
+        auto first = in.data();
+        auto last  = in.data() + in.size();
+
+        const int small = 100; // arbitrary
+        Container c;
+        c.reserve(size + small); // ensure no reallocation
+        std::generate_n(std::back_inserter(c), small, gen);
+
+        for ([[maybe_unused]] auto _ : st) {
+          c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
+          DoNotOptimizeData(c);
+
+          st.PauseTiming();
+          c.erase(c.begin() + small, c.end()); // avoid growing indefinitely
+          st.ResumeTiming();
+        }
+      });
+
+    // Insert at the start of a vector in a scenario where the vector already
+    // has almost enough capacity to hold all the elements we are inserting,
+    // but does need to reallocate.
+    for (auto gen : generators)
+      bench("insert(begin, input-iter, input-iter) (half filled)" + tostr(gen), [gen](auto& st) {
+        auto const size = st.range(0);
+        std::vector<ValueType> in;
+        std::generate_n(std::back_inserter(in), size, gen);
+        DoNotOptimizeData(in);
+        auto first = in.data();
+        auto last  = in.data() + in.size();
+
+        const int overflow = size / 10; // 10% of elements won't fit in the vector when we insert
+        Container c;
+        for ([[maybe_unused]] auto _ : st) {
+          st.PauseTiming();
+          c = Container();
+          c.reserve(size);
+          std::generate_n(std::back_inserter(c), overflow, gen);
+          st.ResumeTiming();
+
+          c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
+          DoNotOptimizeData(c);
+        }
+      });
+
+    // Insert at the start of a vector in a scenario where the vector can fit a few
+    // more elements, but needs to reallocate almost immediately to fit the remaining
+    // elements.
+    for (auto gen : generators)
+      bench("insert(begin, input-iter, input-iter) (near full)" + tostr(gen), [gen](auto& st) {
+        auto const size = st.range(0);
+        std::vector<ValueType> in;
+        std::generate_n(std::back_inserter(in), size, gen);
+        DoNotOptimizeData(in);
+        auto first = in.data();
+        auto last  = in.data() + in.size();
+
+        auto const overflow = 9 * (size / 10); // 90% of elements won't fit in the vector when we insert
+        Container c;
+        for ([[maybe_unused]] auto _ : st) {
+          st.PauseTiming();
+          c = Container();
+          c.reserve(size);
+          std::generate_n(std::back_inserter(c), overflow, gen);
+          st.ResumeTiming();
+
+          c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
+          DoNotOptimizeData(c);
+        }
+      });
+  }
+
+  /////////////////////////
+  // Variations of push_back
+  /////////////////////////
+  static constexpr bool has_push_back = requires(Container c, ValueType v) { c.push_back(v); };
+  static constexpr bool has_capacity  = requires(Container c) { c.capacity(); };
+  static constexpr bool has_reserve   = requires(Container c) { c.reserve(0); };
+  if constexpr (has_push_back) {
+    if constexpr (has_capacity) {
+      // For containers where we can observe capacity(), push_back a single element
+      // without reserving to ensure the container needs to grow
+      for (auto gen : generators)
+        bench("push_back() (growing)" + tostr(gen), [gen](auto& st) {
+          auto const size = st.range(0);
+          std::vector<ValueType> in;
+          std::generate_n(std::back_inserter(in), size, gen);
+          DoNotOptimizeData(in);
+
+          auto at_capacity = [](Container c) {
+            while (c.size() < c.capacity())
+              c.push_back(c.back());
+            return c;
+          };
+
+          std::vector<Container> c(BatchSize, at_capacity(Container(in.begin(), in.end())));
+          std::vector<Container> const original = c;
+
+          while (st.KeepRunningBatch(BatchSize)) {
+            for (std::size_t i = 0; i != BatchSize; ++i) {
+              c[i].push_back(in[i]);
+              DoNotOptimizeData(c[i]);
+            }
+
+            st.PauseTiming();
+            for (std::size_t i = 0; i != BatchSize; ++i) {
+              c[i] = at_capacity(Container(in.begin(), in.end()));
+              assert(c[i].size() == c[i].capacity());
+            }
+            st.ResumeTiming();
+          }
+        });
+    }
+
+    // For containers where we can reserve, push_back a single element after reserving to
+    // ensure the container doesn't grow
+    if constexpr (has_reserve) {
+      for (auto gen : generators)
+        bench("push_back() (with reserve)" + tostr(gen), [gen](auto& st) {
+          auto const size = st.range(0);
+          std::vector<ValueType> in;
+          std::generate_n(std::back_inserter(in), size, gen);
+          DoNotOptimizeData(in);
+
+          Container c(in.begin(), in.end());
+          // Ensure the container has enough capacity
+          c.reserve(c.size() + BatchSize);
+          DoNotOptimizeData(c);
+
+          while (st.KeepRunningBatch(BatchSize)) {
+            for (std::size_t i = 0; i != BatchSize; ++i) {
+              c.push_back(in[i]);
+            }
+            DoNotOptimizeData(c);
+
+            st.PauseTiming();
+            c.erase(c.end() - BatchSize, c.end());
+            st.ResumeTiming();
+          }
+        });
+    }
+
+    // push_back many elements: this is amortized constant for std::vector but not all containers
+    for (auto gen : generators)
+      bench("push_back() (many elements)" + tostr(gen), [gen](auto& st) {
+        auto const size = st.range(0);
+        std::vector<ValueType> in;
+        std::generate_n(std::back_inserter(in), size, gen);
+        DoNotOptimizeData(in);
+
+        Container c;
+        DoNotOptimizeData(c);
+        while (st.KeepRunningBatch(size)) {
+          for (int i = 0; i != size; ++i) {
+            c.push_back(in[i]);
+          }
+          DoNotOptimizeData(c);
+
+          st.PauseTiming();
+          c.clear();
+          st.ResumeTiming();
+        }
+      });
+  }
+
+  /////////////////////////
+  // Erasure
+  /////////////////////////
+  for (auto gen : generators)
+    bench("erase(begin)" + tostr(gen), [gen](auto& st) {
+      auto const size = st.range(0);
+      std::vector<ValueType> in;
+      std::generate_n(std::back_inserter(in), size, gen);
+      DoNotOptimizeData(in);
+
+      Container c(in.begin(), in.end());
+      DoNotOptimizeData(c);
+
+      ValueType value = gen();
+      benchmark::DoNotOptimize(value);
+
+      for ([[maybe_unused]] auto _ : st) {
+        c.erase(c.begin());
+        DoNotOptimizeData(c);
+
+        c.insert(c.end(), value); // re-insert an element at the end to avoid needing a new container
+      }
+    });
+
+  if constexpr (std::random_access_iterator<typename Container::iterator>) {
+    for (auto gen : generators)
+      bench("erase(middle)" + tostr(gen), [gen](auto& st) {
+        auto const size = st.range(0);
+        std::vector<ValueType> in;
+        std::generate_n(std::back_inserter(in), size, gen);
+        DoNotOptimizeData(in);
+
+        Container c(in.begin(), in.end());
+        DoNotOptimizeData(c);
+
+        ValueType value = gen();
+        benchmark::DoNotOptimize(value);
+
+        for ([[maybe_unused]] auto _ : st) {
+          auto mid = c.begin() + (size / 2);
+          c.erase(mid);
+          DoNotOptimizeData(c);
+
+          c.insert(c.end(), value); // re-insert an element at the end to avoid needing a new container
+        }
+      });
+  }
+}
+
+} // namespace support
+
+#endif // TEST_BENCHMARKS_CONTAINERS_SEQUENCE_SEQUENCE_CONTAINER_BENCHMARKS_H
diff --git a/libcxx/test/benchmarks/containers/vector.bench.cpp b/libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
similarity index 73%
rename from libcxx/test/benchmarks/containers/vector.bench.cpp
rename to libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
index eef23d298164287..599db1d90fa9abc 100644
--- a/libcxx/test/benchmarks/containers/vector.bench.cpp
+++ b/libcxx/test/benchmarks/containers/sequence/vector.bench.cpp
@@ -11,12 +11,12 @@
 #include <string>
 #include <vector>
 
-#include "container_benchmarks.h"
+#include "sequence_container_benchmarks.h"
 #include "benchmark/benchmark.h"
 
 int main(int argc, char** argv) {
-  ContainerBenchmarks::sequence_container_benchmarks<std::vector<int>>("std::vector<int>");
-  ContainerBenchmarks::sequence_container_benchmarks<std::vector<std::string>>("std::vector<std::string>");
+  support::sequence_container_benchmarks<std::vector<int>>("std::vector<int>");
+  support::sequence_container_benchmarks<std::vector<std::string>>("std::vector<std::string>");
 
   benchmark::Initialize(&argc, argv);
   benchmark::RunSpecifiedBenchmarks();



More information about the libcxx-commits mailing list