[libcxx-commits] [libcxx] [libc++] Refactor the sequence container benchmarks (PR #119763)
Louis Dionne via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Jan 20 10:26:01 PST 2025
https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/119763
>From 3e70df8634f97bd7b30afe97a233eb4344426d49 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 12 Dec 2024 08:25:15 -0500
Subject: [PATCH 1/9] [libc++] Refactor the sequence container benchmarks
Rewrite the sequence container benchmarks to only rely on the actual
operations specified in SequenceContainer requirements and add benchmarks
for std::list, which is also considered a sequence container.
One of the major goals of this refactoring is also to make these
container benchmarks run faster so that they can be run more frequently.
The existing benchmarks have the significant problem that they take so
long to run that they must basically be run overnight. This patch reduces
the size of inputs such that the rewritten benchmarks each take at most a
minute to run.
This patch doesn't touch the string benchmarks, which were not using
the generic container benchmark functions previously.
---
libcxx/test/benchmarks/Utilities.h | 37 --
.../containers/ContainerBenchmarks.h | 332 ------------
.../containers/container_benchmarks.h | 510 ++++++++++++++++++
.../benchmarks/containers/deque.bench.cpp | 50 +-
.../test/benchmarks/containers/list.bench.cpp | 25 +
...ions.bench.cpp => unordered_set.bench.cpp} | 4 +-
.../benchmarks/containers/vector.bench.cpp | 25 +
.../containers/vector_operations.bench.cpp | 108 ----
8 files changed, 572 insertions(+), 519 deletions(-)
delete mode 100644 libcxx/test/benchmarks/Utilities.h
delete mode 100644 libcxx/test/benchmarks/containers/ContainerBenchmarks.h
create mode 100644 libcxx/test/benchmarks/containers/container_benchmarks.h
create mode 100644 libcxx/test/benchmarks/containers/list.bench.cpp
rename libcxx/test/benchmarks/containers/{unordered_set_operations.bench.cpp => unordered_set.bench.cpp} (99%)
create mode 100644 libcxx/test/benchmarks/containers/vector.bench.cpp
delete mode 100644 libcxx/test/benchmarks/containers/vector_operations.bench.cpp
diff --git a/libcxx/test/benchmarks/Utilities.h b/libcxx/test/benchmarks/Utilities.h
deleted file mode 100644
index fed16ba51f995f..00000000000000
--- a/libcxx/test/benchmarks/Utilities.h
+++ /dev/null
@@ -1,37 +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 BENCHMARK_UTILITIES_H
-#define BENCHMARK_UTILITIES_H
-
-#include <cassert>
-#include <type_traits>
-
-#include "benchmark/benchmark.h"
-
-namespace UtilitiesInternal {
-template <class Container>
-auto HaveDataImpl(int) -> decltype((std::declval<Container&>().data(), std::true_type{}));
-template <class Container>
-auto HaveDataImpl(long) -> std::false_type;
-template <class T>
-using HasData = decltype(HaveDataImpl<T>(0));
-} // namespace UtilitiesInternal
-
-template <class Container, std::enable_if_t<UtilitiesInternal::HasData<Container>::value>* = nullptr>
-void DoNotOptimizeData(Container& c) {
- benchmark::DoNotOptimize(c.data());
-}
-
-template <class Container, std::enable_if_t<!UtilitiesInternal::HasData<Container>::value>* = nullptr>
-void DoNotOptimizeData(Container& c) {
- benchmark::DoNotOptimize(&c);
-}
-
-#endif // BENCHMARK_UTILITIES_H
diff --git a/libcxx/test/benchmarks/containers/ContainerBenchmarks.h b/libcxx/test/benchmarks/containers/ContainerBenchmarks.h
deleted file mode 100644
index 5fc8981619672c..00000000000000
--- a/libcxx/test/benchmarks/containers/ContainerBenchmarks.h
+++ /dev/null
@@ -1,332 +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 BENCHMARK_CONTAINER_BENCHMARKS_H
-#define BENCHMARK_CONTAINER_BENCHMARKS_H
-
-#include <cassert>
-#include <iterator>
-#include <utility>
-
-#include "benchmark/benchmark.h"
-#include "../Utilities.h"
-#include "test_iterators.h"
-
-namespace ContainerBenchmarks {
-
-template <class Container>
-void BM_ConstructSize(benchmark::State& st, Container) {
- auto size = st.range(0);
- for (auto _ : st) {
- Container c(size);
- DoNotOptimizeData(c);
- }
-}
-
-template <class Container>
-void BM_CopyConstruct(benchmark::State& st, Container) {
- auto size = st.range(0);
- Container c(size);
- for (auto _ : st) {
- auto v = c;
- DoNotOptimizeData(v);
- }
-}
-
-template <class Container>
-void BM_Assignment(benchmark::State& st, Container) {
- auto size = st.range(0);
- Container c1;
- Container c2(size);
- for (auto _ : st) {
- c1 = c2;
- DoNotOptimizeData(c1);
- DoNotOptimizeData(c2);
- }
-}
-
-template <std::size_t... sz, typename Container, typename GenInputs>
-void BM_AssignInputIterIter(benchmark::State& st, Container c, GenInputs gen) {
- auto v = gen(1, sz...);
- c.resize(st.range(0), v[0]);
- auto in = gen(st.range(1), sz...);
- benchmark::DoNotOptimize(&in);
- benchmark::DoNotOptimize(&c);
- for (auto _ : st) {
- c.assign(cpp17_input_iterator(in.begin()), cpp17_input_iterator(in.end()));
- benchmark::ClobberMemory();
- }
-}
-
-template <class Container>
-void BM_ConstructSizeValue(benchmark::State& st, Container, typename Container::value_type const& val) {
- const auto size = st.range(0);
- for (auto _ : st) {
- Container c(size, val);
- DoNotOptimizeData(c);
- }
-}
-
-template <class Container, class GenInputs>
-void BM_ConstructIterIter(benchmark::State& st, Container, GenInputs gen) {
- auto in = gen(st.range(0));
- const auto begin = in.begin();
- const auto end = in.end();
- benchmark::DoNotOptimize(&in);
- while (st.KeepRunning()) {
- Container c(begin, end);
- DoNotOptimizeData(c);
- }
-}
-
-template <class Container, class GenInputs>
-void BM_ConstructFromRange(benchmark::State& st, Container, GenInputs gen) {
- auto in = gen(st.range(0));
- benchmark::DoNotOptimize(&in);
- while (st.KeepRunning()) {
- Container c(std::from_range, in);
- DoNotOptimizeData(c);
- }
-}
-
-template <class Container>
-void BM_Pushback_no_grow(benchmark::State& state, Container c) {
- int count = state.range(0);
- c.reserve(count);
- while (state.KeepRunningBatch(count)) {
- c.clear();
- for (int i = 0; i != count; ++i) {
- c.push_back(i);
- }
- benchmark::DoNotOptimize(c.data());
- }
-}
-
-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_Insert_InputIterIter_NoRealloc(benchmark::State& st, Container c, GenInputs gen) {
- auto in = gen(st.range(0));
- DoNotOptimizeData(in);
- const auto size = c.size();
- const auto beg = cpp17_input_iterator(in.begin());
- const auto end = cpp17_input_iterator(in.end());
- c.reserve(size + in.size()); // force no reallocation
- for (auto _ : st) {
- benchmark::DoNotOptimize(&(*c.insert(c.begin(), beg, end)));
- st.PauseTiming();
- c.erase(c.begin() + size, c.end()); // avoid the container to grow indefinitely
- st.ResumeTiming();
- DoNotOptimizeData(c);
- benchmark::ClobberMemory();
- }
-}
-
-template <class Container, class GenInputs>
-void BM_Insert_InputIterIter_Realloc_HalfFilled(benchmark::State& st, Container, GenInputs gen) {
- const auto size = st.range(0);
- Container a = gen(size);
- Container in = gen(size + 10);
- DoNotOptimizeData(a);
- DoNotOptimizeData(in);
- const auto beg = cpp17_input_iterator(in.begin());
- const auto end = cpp17_input_iterator(in.end());
- for (auto _ : st) {
- st.PauseTiming();
- Container c;
- c.reserve(size * 2); // Reallocation with half-filled container
- c = a;
- st.ResumeTiming();
- benchmark::DoNotOptimize(&(*c.insert(c.begin(), beg, end)));
- DoNotOptimizeData(c);
- benchmark::ClobberMemory();
- }
-}
-
-template <class Container, class GenInputs>
-void BM_Insert_InputIterIter_Realloc_NearFull(benchmark::State& st, Container, GenInputs gen) {
- const auto size = st.range(0);
- Container a = gen(size);
- Container in = gen(10);
- DoNotOptimizeData(a);
- DoNotOptimizeData(in);
- const auto beg = cpp17_input_iterator(in.begin());
- const auto end = cpp17_input_iterator(in.end());
- for (auto _ : st) {
- st.PauseTiming();
- Container c;
- c.reserve(size + 5); // Reallocation almost-full container
- c = a;
- st.ResumeTiming();
- benchmark::DoNotOptimize(&(*c.insert(c.begin(), beg, end)));
- DoNotOptimizeData(c);
- 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_erase_iter_in_middle(benchmark::State& st, Container, GenInputs gen) {
- auto in = gen(st.range(0));
- Container c(in.begin(), in.end());
- assert(c.size() > 2);
- for (auto _ : st) {
- auto mid = std::next(c.begin(), c.size() / 2);
- auto tmp = *mid;
- auto result = c.erase(mid); // erase an element in the middle
- benchmark::DoNotOptimize(result);
- c.push_back(std::move(tmp)); // and then push it back at the end to avoid needing a new container
- }
-}
-
-template <class Container, class GenInputs>
-void BM_erase_iter_at_start(benchmark::State& st, Container, GenInputs gen) {
- auto in = gen(st.range(0));
- Container c(in.begin(), in.end());
- assert(c.size() > 2);
- for (auto _ : st) {
- auto it = c.begin();
- auto tmp = *it;
- auto result = c.erase(it); // erase the first element
- benchmark::DoNotOptimize(result);
- c.push_back(std::move(tmp)); // and then push it back at the end to avoid needing a new container
- }
-}
-
-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 // BENCHMARK_CONTAINER_BENCHMARKS_H
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
new file mode 100644
index 00000000000000..0b65baaaeb777f
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -0,0 +1,510 @@
+// -*- 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 <cstddef>
+#include <iterator>
+#include <ranges> // for std::from_range
+#include <string>
+#include <vector>
+
+#include "benchmark/benchmark.h"
+#include "test_iterators.h"
+#include "test_macros.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);
+ char buffer[sizeof(Container)];
+ for (auto _ : st) {
+ std::construct_at(reinterpret_cast<Container*>(buffer), size);
+ benchmark::DoNotOptimize(buffer);
+ st.PauseTiming();
+ std::destroy_at(reinterpret_cast<Container*>(buffer));
+ st.ResumeTiming();
+ }
+}
+
+template <class Container>
+void BM_ctor_size_value(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const auto size = st.range(0);
+ ValueType value{};
+ benchmark::DoNotOptimize(value);
+ char buffer[sizeof(Container)];
+ for (auto _ : st) {
+ std::construct_at(reinterpret_cast<Container*>(buffer), size, value);
+ benchmark::DoNotOptimize(buffer);
+ st.PauseTiming();
+ std::destroy_at(reinterpret_cast<Container*>(buffer));
+ st.ResumeTiming();
+ }
+}
+
+template <class Container>
+void BM_ctor_iter_iter(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const auto size = st.range(0);
+ std::vector<ValueType> in(size);
+ const auto begin = in.begin();
+ const auto end = in.end();
+ benchmark::DoNotOptimize(in);
+ char buffer[sizeof(Container)];
+ for (auto _ : st) {
+ std::construct_at(reinterpret_cast<Container*>(buffer), begin, end);
+ benchmark::DoNotOptimize(buffer);
+ st.PauseTiming();
+ std::destroy_at(reinterpret_cast<Container*>(buffer));
+ st.ResumeTiming();
+ }
+}
+
+#if TEST_STD_VER >= 23
+template <class Container>
+void BM_ctor_from_range(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const auto size = st.range(0);
+ std::vector<ValueType> in(size);
+ benchmark::DoNotOptimize(in);
+ char buffer[sizeof(Container)];
+ for (auto _ : st) {
+ std::construct_at(reinterpret_cast<Container*>(buffer), std::from_range, in);
+ benchmark::DoNotOptimize(buffer);
+ st.PauseTiming();
+ std::destroy_at(reinterpret_cast<Container*>(buffer));
+ st.ResumeTiming();
+ }
+}
+#endif
+
+template <class Container>
+void BM_ctor_copy(benchmark::State& st) {
+ auto size = st.range(0);
+ Container c(size);
+ char buffer[sizeof(Container)];
+ for (auto _ : st) {
+ std::construct_at(reinterpret_cast<Container*>(buffer), c);
+ benchmark::DoNotOptimize(buffer);
+ st.PauseTiming();
+ std::destroy_at(reinterpret_cast<Container*>(buffer));
+ st.ResumeTiming();
+ }
+}
+
+template <class Container>
+void BM_assignment(benchmark::State& st) {
+ auto size = st.range(0);
+ Container c1;
+ Container c2(size);
+ for (auto _ : st) {
+ c1 = c2;
+ DoNotOptimizeData(c1);
+ DoNotOptimizeData(c2);
+ }
+}
+
+template <typename Container>
+void BM_assign_inputiter(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ auto size = st.range(0);
+ std::vector<ValueType> inputs(size);
+ Container c(inputs.begin(), inputs.end());
+ DoNotOptimizeData(c);
+ DoNotOptimizeData(inputs);
+ ValueType* first = inputs.data();
+ ValueType* last = inputs.data() + inputs.size();
+
+ for (auto _ : st) {
+ c.assign(cpp17_input_iterator(first), cpp17_input_iterator(last));
+ benchmark::ClobberMemory();
+ }
+}
+
+template <class Container>
+void BM_insert_start(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ Container c(inputs.begin(), inputs.end());
+ DoNotOptimizeData(c);
+
+ ValueType value{};
+ 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>
+ requires std::random_access_iterator<typename Container::iterator>
+void BM_insert_middle(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ Container c(inputs.begin(), inputs.end());
+ DoNotOptimizeData(c);
+
+ ValueType value{};
+ benchmark::DoNotOptimize(value);
+
+ for (auto _ : st) {
+ auto mid = c.begin() + (count / 2); // requires random-access iterators in order to make sense
+ c.insert(mid, value);
+ DoNotOptimizeData(c);
+
+ c.erase(c.end() - 1); // avoid growing indefinitely
+ }
+}
+
+template <class Container>
+void BM_insert_input_iter_with_reserve_no_realloc(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ const auto beg = cpp17_input_iterator(inputs.begin());
+ const auto end = cpp17_input_iterator(inputs.end());
+
+ auto size = 100; // arbitrary
+ Container c(size);
+ c.reserve(size + inputs.size()); // ensure no reallocation
+ for (auto _ : st) {
+ c.insert(c.begin(), beg, end);
+ DoNotOptimizeData(c);
+
+ st.PauseTiming();
+ c.erase(c.begin() + size, c.end()); // avoid growing indefinitely
+ st.ResumeTiming();
+ }
+}
+
+template <class Container>
+void BM_insert_input_iter_with_reserve_half_filled(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ const auto beg = cpp17_input_iterator(inputs.begin());
+ const auto end = cpp17_input_iterator(inputs.end());
+
+ for (auto _ : st) {
+ st.PauseTiming();
+ Container c(count / 2);
+ // Half the elements in [beg, end) can fit in the vector without reallocation, so we'll reallocate halfway through
+ c.reserve(count);
+ st.ResumeTiming();
+
+ c.insert(c.begin(), beg, end);
+ DoNotOptimizeData(c);
+ }
+}
+
+template <class Container>
+void BM_insert_input_iter_with_reserve_near_full(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ const auto beg = cpp17_input_iterator(inputs.begin());
+ const auto end = cpp17_input_iterator(inputs.end());
+
+ for (auto _ : st) {
+ st.PauseTiming();
+ Container c(count);
+ c.reserve(count + 5); // Make sure the container is almost full
+ st.ResumeTiming();
+
+ c.insert(c.begin(), beg, end);
+ DoNotOptimizeData(c);
+ }
+}
+
+template <class Container>
+void BM_erase_start(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ Container c(inputs.begin(), inputs.end());
+ DoNotOptimizeData(c);
+
+ ValueType value{};
+ 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>
+ requires std::random_access_iterator<typename Container::iterator>
+void BM_erase_middle(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ Container c(inputs.begin(), inputs.end());
+ DoNotOptimizeData(c);
+
+ ValueType value{};
+ benchmark::DoNotOptimize(value);
+
+ for (auto _ : st) {
+ auto mid = c.begin() + (count / 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>
+void sequence_container_benchmarks(std::string container) {
+ // constructors
+ benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size<Container>)->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::ctor(size, value_type)", BM_ctor_size_value<Container>)->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator)", BM_ctor_iter_iter<Container>)->Arg(1024);
+#if TEST_STD_VER >= 23
+ benchmark::RegisterBenchmark(container + "::ctor(Range)", BM_ctor_from_range<Container>)->Arg(1024);
+#endif
+ benchmark::RegisterBenchmark(container + "::ctor(const&)", BM_ctor_copy<Container>)->Arg(1024);
+
+ // assignment
+ benchmark::RegisterBenchmark(container + "::operator=", BM_assignment<Container>)->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::assign(input-iter, input-iter)", BM_assign_inputiter<Container>)
+ ->Arg(1024);
+
+ // insert
+ benchmark::RegisterBenchmark(container + "::insert(start)", BM_insert_start<Container>)->Arg(1024);
+ if constexpr (std::random_access_iterator<typename Container::iterator>) {
+ benchmark::RegisterBenchmark(container + "::insert(middle)", BM_insert_middle<Container>)->Arg(1024);
+ }
+ if constexpr (requires(Container c) { c.reserve(0); }) {
+ benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (no realloc)",
+ BM_insert_input_iter_with_reserve_no_realloc<Container>)
+ ->Arg(514048);
+ benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (half filled)",
+ BM_insert_input_iter_with_reserve_no_realloc<Container>)
+ ->Arg(514048);
+ benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (near full)",
+ BM_insert_input_iter_with_reserve_near_full<Container>)
+ ->Arg(514048);
+ }
+
+ // erase
+ benchmark::RegisterBenchmark(container + "::erase(start)", BM_erase_start<Container>)->Arg(1024);
+ if constexpr (std::random_access_iterator<typename Container::iterator>) {
+ benchmark::RegisterBenchmark(container + "::erase(middle)", BM_erase_middle<Container>)->Arg(1024);
+ }
+}
+
+//
+// "Back-insertable" sequence container operations
+//
+template <class Container>
+void BM_push_back(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ benchmark::DoNotOptimize(inputs);
+
+ Container c;
+ DoNotOptimizeData(c);
+ while (st.KeepRunningBatch(count)) {
+ c.clear();
+ for (int i = 0; i != count; ++i) {
+ c.push_back(inputs[i]);
+ }
+ DoNotOptimizeData(c);
+ }
+}
+
+template <class Container>
+void BM_push_back_with_reserve(benchmark::State& st) {
+ using ValueType = typename Container::value_type;
+ const int count = st.range(0);
+ std::vector<ValueType> inputs(count);
+ benchmark::DoNotOptimize(inputs);
+
+ Container c;
+ c.reserve(count);
+ DoNotOptimizeData(c);
+ while (st.KeepRunningBatch(count)) {
+ c.clear();
+ for (int i = 0; i != count; ++i) {
+ c.push_back(inputs[i]);
+ }
+ DoNotOptimizeData(c);
+ }
+}
+
+template <class Container>
+void back_insertable_container_benchmarks(std::string container) {
+ sequence_container_benchmarks<Container>(container);
+ benchmark::RegisterBenchmark(container + "::push_back()", BM_push_back<Container>)->Arg(1024);
+ if constexpr (requires(Container c) { c.reserve(0); }) {
+ benchmark::RegisterBenchmark(container + "::push_back() (with reserve)", BM_push_back_with_reserve<Container>)
+ ->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/deque.bench.cpp
index 7ff1093a9391ca..66e54070c6dcf8 100644
--- a/libcxx/test/benchmarks/containers/deque.bench.cpp
+++ b/libcxx/test/benchmarks/containers/deque.bench.cpp
@@ -6,50 +6,20 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: c++03, c++11, c++14, c++17
#include <deque>
#include <string>
+#include "container_benchmarks.h"
#include "benchmark/benchmark.h"
-#include "ContainerBenchmarks.h"
-#include "../GenerateInput.h"
+int main(int argc, char** argv) {
+ ContainerBenchmarks::back_insertable_container_benchmarks<std::deque<int>>("std::deque<int>");
+ ContainerBenchmarks::back_insertable_container_benchmarks<std::deque<std::string>>("std::deque<std::string>");
-using namespace ContainerBenchmarks;
-
-constexpr std::size_t TestNumInputs = 1024;
-
-BENCHMARK_CAPTURE(BM_ConstructSize, deque_byte, std::deque<unsigned char>{})->Arg(5140480);
-
-BENCHMARK_CAPTURE(BM_ConstructSizeValue, deque_byte, std::deque<unsigned char>{}, 0)->Arg(5140480);
-
-BENCHMARK_CAPTURE(BM_ConstructIterIter, deque_char, std::deque<char>{}, getRandomIntegerInputs<char>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructIterIter, deque_size_t, std::deque<size_t>{}, getRandomIntegerInputs<size_t>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructIterIter, deque_string, std::deque<std::string>{}, getRandomStringInputs)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_char, std::deque<char>{}, getRandomIntegerInputs<char>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_size_t, std::deque<size_t>{}, getRandomIntegerInputs<size_t>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_string, std::deque<std::string>{}, getRandomStringInputs)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_int, std::deque<int>{}, getRandomIntegerInputs<int>)
- ->Range(TestNumInputs, TestNumInputs * 10);
-BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_string, std::deque<std::string>{}, getRandomStringInputs)
- ->Range(TestNumInputs, TestNumInputs * 10);
-
-BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_int, std::deque<int>{}, getRandomIntegerInputs<int>)
- ->Range(TestNumInputs, TestNumInputs * 10);
-BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_string, std::deque<std::string>{}, getRandomStringInputs)
- ->Range(TestNumInputs, TestNumInputs * 10);
-
-BENCHMARK_MAIN();
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/list.bench.cpp b/libcxx/test/benchmarks/containers/list.bench.cpp
new file mode 100644
index 00000000000000..2212affa02ba4e
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/list.bench.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <list>
+#include <string>
+
+#include "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>");
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/unordered_set_operations.bench.cpp b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
similarity index 99%
rename from libcxx/test/benchmarks/containers/unordered_set_operations.bench.cpp
rename to libcxx/test/benchmarks/containers/unordered_set.bench.cpp
index a8448ef5a0cfb9..ad8d0feaa04365 100644
--- a/libcxx/test/benchmarks/containers/unordered_set_operations.bench.cpp
+++ b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: c++03, c++11, c++14, c++17
#include <cstdint>
#include <cstdlib>
@@ -17,7 +17,7 @@
#include "benchmark/benchmark.h"
-#include "ContainerBenchmarks.h"
+#include "container_benchmarks.h"
#include "../GenerateInput.h"
#include "test_macros.h"
diff --git a/libcxx/test/benchmarks/containers/vector.bench.cpp b/libcxx/test/benchmarks/containers/vector.bench.cpp
new file mode 100644
index 00000000000000..22b62585b74f78
--- /dev/null
+++ b/libcxx/test/benchmarks/containers/vector.bench.cpp
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+#include <string>
+#include <vector>
+
+#include "container_benchmarks.h"
+#include "benchmark/benchmark.h"
+
+int main(int argc, char** argv) {
+ ContainerBenchmarks::back_insertable_container_benchmarks<std::vector<int>>("std::vector<int>");
+ ContainerBenchmarks::back_insertable_container_benchmarks<std::vector<std::string>>("std::vector<std::string>");
+
+ benchmark::Initialize(&argc, argv);
+ benchmark::RunSpecifiedBenchmarks();
+ benchmark::Shutdown();
+ return 0;
+}
diff --git a/libcxx/test/benchmarks/containers/vector_operations.bench.cpp b/libcxx/test/benchmarks/containers/vector_operations.bench.cpp
deleted file mode 100644
index 1cd754ca7e7803..00000000000000
--- a/libcxx/test/benchmarks/containers/vector_operations.bench.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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
-//
-//===----------------------------------------------------------------------===//
-
-// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
-
-#include <cstdint>
-#include <cstdlib>
-#include <cstring>
-#include <deque>
-#include <functional>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "benchmark/benchmark.h"
-#include "ContainerBenchmarks.h"
-#include "../GenerateInput.h"
-
-using namespace ContainerBenchmarks;
-
-constexpr std::size_t TestNumInputs = 1024;
-
-BENCHMARK_CAPTURE(BM_ConstructSize, vector_byte, std::vector<unsigned char>{})->Arg(5140480);
-
-BENCHMARK_CAPTURE(BM_CopyConstruct, vector_int, std::vector<int>{})->Arg(5140480);
-
-BENCHMARK_CAPTURE(BM_Assignment, vector_int, std::vector<int>{})->Arg(5140480);
-
-BENCHMARK_CAPTURE(BM_ConstructSizeValue, vector_byte, std::vector<unsigned char>{}, 0)->Arg(5140480);
-
-BENCHMARK_CAPTURE(BM_ConstructIterIter, vector_char, std::vector<char>{}, getRandomIntegerInputs<char>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructIterIter, vector_size_t, std::vector<size_t>{}, getRandomIntegerInputs<size_t>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructIterIter, vector_string, std::vector<std::string>{}, getRandomStringInputs)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_char, std::vector<char>{}, getRandomIntegerInputs<char>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_size_t, std::vector<size_t>{}, getRandomIntegerInputs<size_t>)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_string, std::vector<std::string>{}, getRandomStringInputs)
- ->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_Pushback_no_grow, vector_int, std::vector<int>{})->Arg(TestNumInputs);
-
-BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
- ->Range(TestNumInputs, TestNumInputs * 10);
-BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_string, std::vector<std::string>{}, getRandomStringInputs)
- ->Range(TestNumInputs, TestNumInputs * 10);
-
-BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
- ->Range(TestNumInputs, TestNumInputs * 10);
-BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_string, std::vector<std::string>{}, getRandomStringInputs)
- ->Range(TestNumInputs, TestNumInputs * 10);
-
-template <class T>
-void bm_grow(benchmark::State& state) {
- for (auto _ : state) {
- std::vector<T> vec;
- benchmark::DoNotOptimize(vec);
- for (size_t i = 0; i != 2048; ++i)
- vec.emplace_back();
- benchmark::DoNotOptimize(vec);
- }
-}
-BENCHMARK(bm_grow<int>);
-BENCHMARK(bm_grow<std::string>);
-BENCHMARK(bm_grow<std::unique_ptr<int>>);
-BENCHMARK(bm_grow<std::deque<int>>);
-
-BENCHMARK_CAPTURE(BM_AssignInputIterIter, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
- ->Args({TestNumInputs, TestNumInputs});
-
-BENCHMARK_CAPTURE(
- BM_AssignInputIterIter<32>, vector_string, std::vector<std::string>{}, getRandomStringInputsWithLength)
- ->Args({TestNumInputs, TestNumInputs});
-
-BENCHMARK_CAPTURE(BM_AssignInputIterIter<100>,
- vector_vector_int,
- std::vector<std::vector<int>>{},
- getRandomIntegerInputsWithLength<int>)
- ->Args({TestNumInputs, TestNumInputs});
-
-BENCHMARK_CAPTURE(BM_Insert_InputIterIter_NoRealloc, vector_int, std::vector<int>(100, 1), getRandomIntegerInputs<int>)
- ->Arg(514048);
-BENCHMARK_CAPTURE(
- BM_Insert_InputIterIter_Realloc_HalfFilled, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
- ->Arg(514048);
-BENCHMARK_CAPTURE(BM_Insert_InputIterIter_Realloc_NearFull, vector_int, std::vector<int>{}, getRandomIntegerInputs<int>)
- ->Arg(514048);
-BENCHMARK_CAPTURE(
- BM_Insert_InputIterIter_Realloc_HalfFilled, vector_string, std::vector<std::string>{}, getSSORandomStringInputs)
- ->Arg(514048);
-BENCHMARK_CAPTURE(
- BM_Insert_InputIterIter_Realloc_NearFull, vector_string, std::vector<std::string>{}, getSSORandomStringInputs)
- ->Arg(514048);
-
-BENCHMARK_MAIN();
>From 78968216fd6bf99fcc0bce25ed646bd779f22a7b Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 14 Jan 2025 19:06:08 -0500
Subject: [PATCH 2/9] Draft of controlling what kind of elements are being used
for benchmarks (with the goal of benchmarking SSO vs non-SSO values mainly)
---
libcxx/test/benchmarks/GenerateInput.h | 27 +++++++++++++++
.../containers/container_benchmarks.h | 33 ++++++++++++++-----
2 files changed, 52 insertions(+), 8 deletions(-)
diff --git a/libcxx/test/benchmarks/GenerateInput.h b/libcxx/test/benchmarks/GenerateInput.h
index 6d5c5167e91ed8..c2b034162671c6 100644
--- a/libcxx/test/benchmarks/GenerateInput.h
+++ b/libcxx/test/benchmarks/GenerateInput.h
@@ -171,4 +171,31 @@ inline std::vector<const char*> getRandomCStringInputs(std::size_t N) {
return cinputs;
}
+template <class T>
+struct Generate {
+ // When the contents don't matter
+ static T arbitrary();
+
+ // Prefer a cheap-to-construct element if possible
+ static T cheap();
+
+ // Prefer an expensive-to-construct element if possible
+ static T expensive();
+};
+
+template <class T>
+ requires std::integral<T>
+struct Generate<T> {
+ static T arbitrary() { return 42; }
+ static T cheap() { return 42; }
+ static T expensive() { return 42; }
+};
+
+template <>
+struct Generate<std::string> {
+ static std::string arbitrary() { return "hello world"; }
+ static std::string cheap() { return "small"; }
+ static std::string expensive() { return "large stringggggggggggggggggggggggggggggggggggggggggggggggggggg"; }
+};
+
#endif // BENCHMARK_GENERATE_INPUT_H
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index 0b65baaaeb777f..1bdfc42115bf09 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -10,6 +10,7 @@
#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
@@ -19,6 +20,7 @@
#include "benchmark/benchmark.h"
#include "test_iterators.h"
#include "test_macros.h"
+#include "../GenerateInput.h"
namespace ContainerBenchmarks {
@@ -47,11 +49,11 @@ void BM_ctor_size(benchmark::State& st) {
}
}
-template <class Container>
-void BM_ctor_size_value(benchmark::State& st) {
+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{};
+ ValueType value = gen();
benchmark::DoNotOptimize(value);
char buffer[sizeof(Container)];
for (auto _ : st) {
@@ -63,11 +65,12 @@ void BM_ctor_size_value(benchmark::State& st) {
}
}
-template <class Container>
-void BM_ctor_iter_iter(benchmark::State& st) {
+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(size);
+ 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);
@@ -283,10 +286,24 @@ void BM_erase_middle(benchmark::State& st) {
template <class Container>
void sequence_container_benchmarks(std::string container) {
+ using ValueType = typename Container::value_type;
+ auto cheap = [] { return Generate<ValueType>::cheap(); };
+ auto expensive = [] { return Generate<ValueType>::expensive(); };
+
// constructors
benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size<Container>)->Arg(1024);
- benchmark::RegisterBenchmark(container + "::ctor(size, value_type)", BM_ctor_size_value<Container>)->Arg(1024);
- benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator)", BM_ctor_iter_iter<Container>)->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (cheap elements)", [=](auto& st) {
+ BM_ctor_size_value<Container>(st, cheap);
+ })->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (expensive elements)", [=](auto& st) {
+ BM_ctor_size_value<Container>(st, expensive);
+ })->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (cheap elements)", [=](auto& st) {
+ BM_ctor_iter_iter<Container>(st, cheap);
+ })->Arg(1024);
+ benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (expensive elements)", [=](auto& st) {
+ BM_ctor_iter_iter<Container>(st, expensive);
+ })->Arg(1024);
#if TEST_STD_VER >= 23
benchmark::RegisterBenchmark(container + "::ctor(Range)", BM_ctor_from_range<Container>)->Arg(1024);
#endif
>From 238244f09b85412a92639f6b0a8d696aeabf6923 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 16 Jan 2025 12:45:46 -0500
Subject: [PATCH 3/9] Generators everywhere, polish output
---
libcxx/test/benchmarks/GenerateInput.h | 2 +-
.../containers/container_benchmarks.h | 445 ++++++++++--------
.../benchmarks/containers/deque.bench.cpp | 4 +-
.../benchmarks/containers/vector.bench.cpp | 4 +-
4 files changed, 262 insertions(+), 193 deletions(-)
diff --git a/libcxx/test/benchmarks/GenerateInput.h b/libcxx/test/benchmarks/GenerateInput.h
index c2b034162671c6..b659aa35412838 100644
--- a/libcxx/test/benchmarks/GenerateInput.h
+++ b/libcxx/test/benchmarks/GenerateInput.h
@@ -195,7 +195,7 @@ template <>
struct Generate<std::string> {
static std::string arbitrary() { return "hello world"; }
static std::string cheap() { return "small"; }
- static std::string expensive() { return "large stringggggggggggggggggggggggggggggggggggggggggggggggggggg"; }
+ static std::string expensive() { return std::string(256, 'x'); }
};
#endif // BENCHMARK_GENERATE_INPUT_H
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index 1bdfc42115bf09..fde148bff9c5d6 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -15,6 +15,7 @@
#include <iterator>
#include <ranges> // for std::from_range
#include <string>
+#include <type_traits>
#include <vector>
#include "benchmark/benchmark.h"
@@ -39,13 +40,10 @@ void DoNotOptimizeData(Container& c) {
template <class Container>
void BM_ctor_size(benchmark::State& st) {
auto size = st.range(0);
- char buffer[sizeof(Container)];
+
for (auto _ : st) {
- std::construct_at(reinterpret_cast<Container*>(buffer), size);
- benchmark::DoNotOptimize(buffer);
- st.PauseTiming();
- std::destroy_at(reinterpret_cast<Container*>(buffer));
- st.ResumeTiming();
+ Container c(size); // we assume the destructor doesn't dominate the benchmark
+ DoNotOptimizeData(c);
}
}
@@ -55,13 +53,10 @@ void BM_ctor_size_value(benchmark::State& st, Generator gen) {
const auto size = st.range(0);
ValueType value = gen();
benchmark::DoNotOptimize(value);
- char buffer[sizeof(Container)];
+
for (auto _ : st) {
- std::construct_at(reinterpret_cast<Container*>(buffer), size, value);
- benchmark::DoNotOptimize(buffer);
- st.PauseTiming();
- std::destroy_at(reinterpret_cast<Container*>(buffer));
- st.ResumeTiming();
+ Container c(size, value); // we assume the destructor doesn't dominate the benchmark
+ DoNotOptimizeData(c);
}
}
@@ -74,86 +69,103 @@ void BM_ctor_iter_iter(benchmark::State& st, Generator gen) {
const auto begin = in.begin();
const auto end = in.end();
benchmark::DoNotOptimize(in);
- char buffer[sizeof(Container)];
+
for (auto _ : st) {
- std::construct_at(reinterpret_cast<Container*>(buffer), begin, end);
- benchmark::DoNotOptimize(buffer);
- st.PauseTiming();
- std::destroy_at(reinterpret_cast<Container*>(buffer));
- st.ResumeTiming();
+ Container c(begin, end); // we assume the destructor doesn't dominate the benchmark
+ DoNotOptimizeData(c);
}
}
#if TEST_STD_VER >= 23
-template <class Container>
-void BM_ctor_from_range(benchmark::State& st) {
+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(size);
+ std::vector<ValueType> in;
+ std::generate_n(std::back_inserter(in), size, gen);
benchmark::DoNotOptimize(in);
- char buffer[sizeof(Container)];
+
for (auto _ : st) {
- std::construct_at(reinterpret_cast<Container*>(buffer), std::from_range, in);
- benchmark::DoNotOptimize(buffer);
- st.PauseTiming();
- std::destroy_at(reinterpret_cast<Container*>(buffer));
- st.ResumeTiming();
+ Container c(std::from_range, in); // we assume the destructor doesn't dominate the benchmark
+ DoNotOptimizeData(c);
}
}
#endif
-template <class Container>
-void BM_ctor_copy(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_ctor_copy(benchmark::State& st, Generator gen) {
auto size = st.range(0);
- Container c(size);
- char buffer[sizeof(Container)];
+ Container in;
+ std::generate_n(std::back_inserter(in), size, gen);
+ DoNotOptimizeData(in);
+
for (auto _ : st) {
- std::construct_at(reinterpret_cast<Container*>(buffer), c);
- benchmark::DoNotOptimize(buffer);
- st.PauseTiming();
- std::destroy_at(reinterpret_cast<Container*>(buffer));
- st.ResumeTiming();
+ Container c(in); // we assume the destructor doesn't dominate the benchmark
+ DoNotOptimizeData(c);
+ DoNotOptimizeData(in);
}
}
-template <class Container>
-void BM_assignment(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_assignment(benchmark::State& st, Generator gen) {
auto size = st.range(0);
- Container c1;
- Container c2(size);
+ 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) {
- c1 = c2;
- DoNotOptimizeData(c1);
- DoNotOptimizeData(c2);
+ c = toggle ? in1 : in2;
+ toggle = !toggle;
+ DoNotOptimizeData(c);
+ DoNotOptimizeData(in1);
+ DoNotOptimizeData(in2);
}
}
-template <typename Container>
-void BM_assign_inputiter(benchmark::State& st) {
+// 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> inputs(size);
- Container c(inputs.begin(), inputs.end());
- DoNotOptimizeData(c);
- DoNotOptimizeData(inputs);
- ValueType* first = inputs.data();
- ValueType* last = inputs.data() + inputs.size();
-
+ 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));
- benchmark::ClobberMemory();
+ toggle = !toggle;
+ DoNotOptimizeData(c);
}
}
-template <class Container>
-void BM_insert_start(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_insert_start(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- Container c(inputs.begin(), inputs.end());
+ 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{};
+ ValueType value = gen();
benchmark::DoNotOptimize(value);
for (auto _ : st) {
@@ -164,20 +176,23 @@ void BM_insert_start(benchmark::State& st) {
}
}
-template <class Container>
+template <class Container, class Generator>
requires std::random_access_iterator<typename Container::iterator>
-void BM_insert_middle(benchmark::State& st) {
+void BM_insert_middle(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- Container c(inputs.begin(), inputs.end());
+ 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{};
+ ValueType value = gen();
benchmark::DoNotOptimize(value);
for (auto _ : st) {
- auto mid = c.begin() + (count / 2); // requires random-access iterators in order to make sense
+ auto mid = c.begin() + (size / 2); // requires random-access iterators in order to make sense
c.insert(mid, value);
DoNotOptimizeData(c);
@@ -185,76 +200,91 @@ void BM_insert_middle(benchmark::State& st) {
}
}
-template <class Container>
-void BM_insert_input_iter_with_reserve_no_realloc(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_insert_start_input_iter_with_reserve_no_realloc(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- const auto beg = cpp17_input_iterator(inputs.begin());
- const auto end = cpp17_input_iterator(inputs.end());
-
- auto size = 100; // arbitrary
- Container c(size);
- c.reserve(size + inputs.size()); // ensure no reallocation
+ 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(), beg, end);
+ c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
DoNotOptimizeData(c);
st.PauseTiming();
- c.erase(c.begin() + size, c.end()); // avoid growing indefinitely
+ c.erase(c.begin() + small, c.end()); // avoid growing indefinitely
st.ResumeTiming();
}
}
-template <class Container>
-void BM_insert_input_iter_with_reserve_half_filled(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_insert_start_input_iter_with_reserve_half_filled(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- const auto beg = cpp17_input_iterator(inputs.begin());
- const auto end = cpp17_input_iterator(inputs.end());
+ 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();
for (auto _ : st) {
st.PauseTiming();
- Container c(count / 2);
// Half the elements in [beg, end) can fit in the vector without reallocation, so we'll reallocate halfway through
- c.reserve(count);
+ Container c;
+ c.reserve(size);
+ std::generate_n(std::back_inserter(c), size / 2, gen);
st.ResumeTiming();
- c.insert(c.begin(), beg, end);
+ c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
DoNotOptimizeData(c);
}
}
-template <class Container>
-void BM_insert_input_iter_with_reserve_near_full(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_insert_start_input_iter_with_reserve_near_full(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- const auto beg = cpp17_input_iterator(inputs.begin());
- const auto end = cpp17_input_iterator(inputs.end());
+ 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();
for (auto _ : st) {
st.PauseTiming();
- Container c(count);
- c.reserve(count + 5); // Make sure the container is almost full
+ // Create an almost full container
+ Container c;
+ c.reserve(size + 5);
+ std::generate_n(std::back_inserter(c), size, gen);
st.ResumeTiming();
- c.insert(c.begin(), beg, end);
+ c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
DoNotOptimizeData(c);
}
}
-template <class Container>
-void BM_erase_start(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_erase_start(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- Container c(inputs.begin(), inputs.end());
+ 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{};
+ ValueType value = gen();
benchmark::DoNotOptimize(value);
+
for (auto _ : st) {
c.erase(c.begin());
DoNotOptimizeData(c);
@@ -263,20 +293,23 @@ void BM_erase_start(benchmark::State& st) {
}
}
-template <class Container>
+template <class Container, class Generator>
requires std::random_access_iterator<typename Container::iterator>
-void BM_erase_middle(benchmark::State& st) {
+void BM_erase_middle(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- Container c(inputs.begin(), inputs.end());
+ 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{};
+ ValueType value = gen();
benchmark::DoNotOptimize(value);
for (auto _ : st) {
- auto mid = c.begin() + (count / 2);
+ auto mid = c.begin() + (size / 2);
c.erase(mid);
DoNotOptimizeData(c);
@@ -284,107 +317,143 @@ void BM_erase_middle(benchmark::State& st) {
}
}
-template <class Container>
-void sequence_container_benchmarks(std::string container) {
- using ValueType = typename Container::value_type;
- auto cheap = [] { return Generate<ValueType>::cheap(); };
- auto expensive = [] { return Generate<ValueType>::expensive(); };
-
- // constructors
- benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size<Container>)->Arg(1024);
- benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (cheap elements)", [=](auto& st) {
- BM_ctor_size_value<Container>(st, cheap);
- })->Arg(1024);
- benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (expensive elements)", [=](auto& st) {
- BM_ctor_size_value<Container>(st, expensive);
- })->Arg(1024);
- benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (cheap elements)", [=](auto& st) {
- BM_ctor_iter_iter<Container>(st, cheap);
- })->Arg(1024);
- benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (expensive elements)", [=](auto& st) {
- BM_ctor_iter_iter<Container>(st, expensive);
- })->Arg(1024);
-#if TEST_STD_VER >= 23
- benchmark::RegisterBenchmark(container + "::ctor(Range)", BM_ctor_from_range<Container>)->Arg(1024);
-#endif
- benchmark::RegisterBenchmark(container + "::ctor(const&)", BM_ctor_copy<Container>)->Arg(1024);
-
- // assignment
- benchmark::RegisterBenchmark(container + "::operator=", BM_assignment<Container>)->Arg(1024);
- benchmark::RegisterBenchmark(container + "::assign(input-iter, input-iter)", BM_assign_inputiter<Container>)
- ->Arg(1024);
-
- // insert
- benchmark::RegisterBenchmark(container + "::insert(start)", BM_insert_start<Container>)->Arg(1024);
- if constexpr (std::random_access_iterator<typename Container::iterator>) {
- benchmark::RegisterBenchmark(container + "::insert(middle)", BM_insert_middle<Container>)->Arg(1024);
- }
- if constexpr (requires(Container c) { c.reserve(0); }) {
- benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (no realloc)",
- BM_insert_input_iter_with_reserve_no_realloc<Container>)
- ->Arg(514048);
- benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (half filled)",
- BM_insert_input_iter_with_reserve_no_realloc<Container>)
- ->Arg(514048);
- benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (near full)",
- BM_insert_input_iter_with_reserve_near_full<Container>)
- ->Arg(514048);
- }
-
- // erase
- benchmark::RegisterBenchmark(container + "::erase(start)", BM_erase_start<Container>)->Arg(1024);
- if constexpr (std::random_access_iterator<typename Container::iterator>) {
- benchmark::RegisterBenchmark(container + "::erase(middle)", BM_erase_middle<Container>)->Arg(1024);
- }
-}
-
-//
-// "Back-insertable" sequence container operations
-//
-template <class Container>
-void BM_push_back(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_push_back(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- benchmark::DoNotOptimize(inputs);
+ 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(count)) {
+ while (st.KeepRunningBatch(size)) {
c.clear();
- for (int i = 0; i != count; ++i) {
- c.push_back(inputs[i]);
+ for (int i = 0; i != size; ++i) {
+ c.push_back(in[i]);
}
DoNotOptimizeData(c);
}
}
-template <class Container>
-void BM_push_back_with_reserve(benchmark::State& st) {
+template <class Container, class Generator>
+void BM_push_back_with_reserve(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
- const int count = st.range(0);
- std::vector<ValueType> inputs(count);
- benchmark::DoNotOptimize(inputs);
+ 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(count);
+ c.reserve(size);
DoNotOptimizeData(c);
- while (st.KeepRunningBatch(count)) {
+ while (st.KeepRunningBatch(size)) {
c.clear();
- for (int i = 0; i != count; ++i) {
- c.push_back(inputs[i]);
+ for (int i = 0; i != size; ++i) {
+ c.push_back(in[i]);
}
DoNotOptimizeData(c);
}
}
template <class Container>
-void back_insertable_container_benchmarks(std::string container) {
- sequence_container_benchmarks<Container>(container);
- benchmark::RegisterBenchmark(container + "::push_back()", BM_push_back<Container>)->Arg(1024);
- if constexpr (requires(Container c) { c.reserve(0); }) {
- benchmark::RegisterBenchmark(container + "::push_back() (with reserve)", BM_push_back_with_reserve<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
+ 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_start<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(input-iter, input-iter) (insert at front, no realloc)" + tostr(gen),
+ [=](auto& st) { BM_insert_start_input_iter_with_reserve_no_realloc<Container>(st, gen); })
+ ->Arg(1024);
+ for (auto gen : generators)
+ benchmark::RegisterBenchmark(
+ container + "::insert(input-iter, input-iter) (insert at front, half filled)" + tostr(gen),
+ [=](auto& st) { BM_insert_start_input_iter_with_reserve_half_filled<Container>(st, gen); })
+ ->Arg(1024);
+ for (auto gen : generators)
+ benchmark::RegisterBenchmark(
+ container + "::insert(input-iter, input-iter) (insert at front, near full)" + tostr(gen),
+ [=](auto& st) { BM_insert_start_input_iter_with_reserve_near_full<Container>(st, gen); })
+ ->Arg(1024);
+ }
+
+ // erase
+ for (auto gen : generators)
+ benchmark::RegisterBenchmark(container + "::erase(start)" + tostr(gen), [=](auto& st) {
+ BM_erase_start<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);
+ }
}
}
diff --git a/libcxx/test/benchmarks/containers/deque.bench.cpp b/libcxx/test/benchmarks/containers/deque.bench.cpp
index 66e54070c6dcf8..6a650fa4dce2ac 100644
--- a/libcxx/test/benchmarks/containers/deque.bench.cpp
+++ b/libcxx/test/benchmarks/containers/deque.bench.cpp
@@ -15,8 +15,8 @@
#include "benchmark/benchmark.h"
int main(int argc, char** argv) {
- ContainerBenchmarks::back_insertable_container_benchmarks<std::deque<int>>("std::deque<int>");
- ContainerBenchmarks::back_insertable_container_benchmarks<std::deque<std::string>>("std::deque<std::string>");
+ ContainerBenchmarks::sequence_container_benchmarks<std::deque<int>>("std::deque<int>");
+ ContainerBenchmarks::sequence_container_benchmarks<std::deque<std::string>>("std::deque<std::string>");
benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
diff --git a/libcxx/test/benchmarks/containers/vector.bench.cpp b/libcxx/test/benchmarks/containers/vector.bench.cpp
index 22b62585b74f78..eef23d29816428 100644
--- a/libcxx/test/benchmarks/containers/vector.bench.cpp
+++ b/libcxx/test/benchmarks/containers/vector.bench.cpp
@@ -15,8 +15,8 @@
#include "benchmark/benchmark.h"
int main(int argc, char** argv) {
- ContainerBenchmarks::back_insertable_container_benchmarks<std::vector<int>>("std::vector<int>");
- ContainerBenchmarks::back_insertable_container_benchmarks<std::vector<std::string>>("std::vector<std::string>");
+ ContainerBenchmarks::sequence_container_benchmarks<std::vector<int>>("std::vector<int>");
+ ContainerBenchmarks::sequence_container_benchmarks<std::vector<std::string>>("std::vector<std::string>");
benchmark::Initialize(&argc, argv);
benchmark::RunSpecifiedBenchmarks();
>From d7c06795a124f9badddc0a11898ad1a03e58b12f Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 16 Jan 2025 16:55:10 -0500
Subject: [PATCH 4/9] Minor output improvements
---
.../containers/container_benchmarks.h | 28 +++++++++----------
1 file changed, 14 insertions(+), 14 deletions(-)
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index fde148bff9c5d6..3daeddc9f5a711 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -155,7 +155,7 @@ void BM_assign_input_iter_full(benchmark::State& st, Generator gen) {
}
template <class Container, class Generator>
-void BM_insert_start(benchmark::State& st, Generator gen) {
+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;
@@ -201,7 +201,7 @@ void BM_insert_middle(benchmark::State& st, Generator gen) {
}
template <class Container, class Generator>
-void BM_insert_start_input_iter_with_reserve_no_realloc(benchmark::State& st, Generator gen) {
+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;
@@ -226,7 +226,7 @@ void BM_insert_start_input_iter_with_reserve_no_realloc(benchmark::State& st, Ge
}
template <class Container, class Generator>
-void BM_insert_start_input_iter_with_reserve_half_filled(benchmark::State& st, Generator gen) {
+void BM_insert_begin_input_iter_with_reserve_half_filled(benchmark::State& st, Generator gen) {
using ValueType = typename Container::value_type;
const int size = st.range(0);
std::vector<ValueType> in;
@@ -249,7 +249,7 @@ void BM_insert_start_input_iter_with_reserve_half_filled(benchmark::State& st, G
}
template <class Container, class Generator>
-void BM_insert_start_input_iter_with_reserve_near_full(benchmark::State& st, Generator gen) {
+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;
@@ -272,7 +272,7 @@ void BM_insert_start_input_iter_with_reserve_near_full(benchmark::State& st, Gen
}
template <class Container, class Generator>
-void BM_erase_start(benchmark::State& st, Generator gen) {
+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;
@@ -404,7 +404,7 @@ void sequence_container_benchmarks(std::string container) {
// insert
for (auto gen : generators)
benchmark::RegisterBenchmark(container + "::insert(begin)" + tostr(gen), [=](auto& st) {
- BM_insert_start<Container>(st, gen);
+ BM_insert_begin<Container>(st, gen);
})->Arg(1024);
if constexpr (std::random_access_iterator<typename Container::iterator>) {
for (auto gen : generators)
@@ -415,25 +415,25 @@ void sequence_container_benchmarks(std::string container) {
if constexpr (requires(Container c) { c.reserve(0); }) {
for (auto gen : generators)
benchmark::RegisterBenchmark(
- container + "::insert(input-iter, input-iter) (insert at front, no realloc)" + tostr(gen),
- [=](auto& st) { BM_insert_start_input_iter_with_reserve_no_realloc<Container>(st, gen); })
+ 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(input-iter, input-iter) (insert at front, half filled)" + tostr(gen),
- [=](auto& st) { BM_insert_start_input_iter_with_reserve_half_filled<Container>(st, gen); })
+ container + "::insert(begin, input-iter, input-iter) (half filled)" + tostr(gen),
+ [=](auto& st) { BM_insert_begin_input_iter_with_reserve_half_filled<Container>(st, gen); })
->Arg(1024);
for (auto gen : generators)
benchmark::RegisterBenchmark(
- container + "::insert(input-iter, input-iter) (insert at front, near full)" + tostr(gen),
- [=](auto& st) { BM_insert_start_input_iter_with_reserve_near_full<Container>(st, gen); })
+ 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(start)" + tostr(gen), [=](auto& st) {
- BM_erase_start<Container>(st, gen);
+ 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)
>From 345ac670526baccf789e84c1d59875ebb8439919 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 16 Jan 2025 17:13:51 -0500
Subject: [PATCH 5/9] Fix insertion benchmarks, hopefully
---
.../containers/container_benchmarks.h | 44 ++++++++++++-------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index 3daeddc9f5a711..4d9ff1bf735e3e 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -200,6 +200,8 @@ void BM_insert_middle(benchmark::State& st, Generator gen) {
}
}
+// 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;
@@ -225,8 +227,11 @@ void BM_insert_begin_input_iter_with_reserve_no_realloc(benchmark::State& st, Ge
}
}
+// 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_half_filled(benchmark::State& st, Generator gen) {
+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;
@@ -235,19 +240,24 @@ void BM_insert_begin_input_iter_with_reserve_half_filled(benchmark::State& st, G
auto first = in.data();
auto last = in.data() + in.size();
- for (auto _ : st) {
- st.PauseTiming();
- // Half the elements in [beg, end) can fit in the vector without reallocation, so we'll reallocate halfway through
- Container c;
- c.reserve(size);
- std::generate_n(std::back_inserter(c), size / 2, gen);
- st.ResumeTiming();
+ const int overflow = size / 10; // 10% of elements won't fit in the vector when we insert
+ Container c;
+ c.reserve(size);
+ std::generate_n(std::back_inserter(c), overflow, gen);
+ for (auto _ : st) {
c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
DoNotOptimizeData(c);
+
+ st.PauseTiming();
+ c.erase(c.begin() + overflow, c.end()); // avoid growing indefinitely
+ st.ResumeTiming();
}
}
+// 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;
@@ -258,16 +268,18 @@ void BM_insert_begin_input_iter_with_reserve_near_full(benchmark::State& st, Gen
auto first = in.data();
auto last = in.data() + in.size();
- for (auto _ : st) {
- st.PauseTiming();
- // Create an almost full container
- Container c;
- c.reserve(size + 5);
- std::generate_n(std::back_inserter(c), size, gen);
- st.ResumeTiming();
+ const int overflow = 9 * (size / 10); // 90% of elements won't fit in the vector when we insert
+ Container c;
+ c.reserve(size);
+ std::generate_n(std::back_inserter(c), overflow, gen);
+ for (auto _ : st) {
c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
DoNotOptimizeData(c);
+
+ st.PauseTiming();
+ c.erase(c.begin() + overflow, c.end()); // avoid growing indefinitely
+ st.ResumeTiming();
}
}
@@ -421,7 +433,7 @@ void sequence_container_benchmarks(std::string container) {
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_half_filled<Container>(st, gen); })
+ [=](auto& st) { BM_insert_begin_input_iter_with_reserve_almost_no_realloc<Container>(st, gen); })
->Arg(1024);
for (auto gen : generators)
benchmark::RegisterBenchmark(
>From 93d9a3d456e112a6379014f176cb187296a21888 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 16 Jan 2025 17:37:45 -0500
Subject: [PATCH 6/9] Make sure ctor(size) is available
---
libcxx/test/benchmarks/containers/container_benchmarks.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index 4d9ff1bf735e3e..7f75ab78bc3447 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -383,7 +383,10 @@ void sequence_container_benchmarks(std::string container) {
}
// constructors
- benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size<Container>)->Arg(1024);
+ 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);
>From 893b9048a664fab9f8325c40305819871fa14833 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 17 Jan 2025 09:46:23 -0500
Subject: [PATCH 7/9] Fix bug in insertion benchmarks
---
.../containers/container_benchmarks.h | 26 +++++++++----------
1 file changed, 12 insertions(+), 14 deletions(-)
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index 7f75ab78bc3447..e24bd767177e87 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -242,16 +242,15 @@ void BM_insert_begin_input_iter_with_reserve_almost_no_realloc(benchmark::State&
const int overflow = size / 10; // 10% of elements won't fit in the vector when we insert
Container c;
- c.reserve(size);
- std::generate_n(std::back_inserter(c), overflow, gen);
-
for (auto _ : st) {
- c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
- DoNotOptimizeData(c);
-
st.PauseTiming();
- c.erase(c.begin() + overflow, c.end()); // avoid growing indefinitely
+ 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);
}
}
@@ -270,16 +269,15 @@ void BM_insert_begin_input_iter_with_reserve_near_full(benchmark::State& st, Gen
const int overflow = 9 * (size / 10); // 90% of elements won't fit in the vector when we insert
Container c;
- c.reserve(size);
- std::generate_n(std::back_inserter(c), overflow, gen);
-
for (auto _ : st) {
- c.insert(c.begin(), cpp17_input_iterator(first), cpp17_input_iterator(last));
- DoNotOptimizeData(c);
-
st.PauseTiming();
- c.erase(c.begin() + overflow, c.end()); // avoid growing indefinitely
+ 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);
}
}
>From dfda2470128213d8296a77a863252958f3a1245e Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Fri, 17 Jan 2025 16:18:26 -0500
Subject: [PATCH 8/9] Add missing include
---
libcxx/test/benchmarks/containers/container_benchmarks.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index e24bd767177e87..c4a769f19c3173 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -11,6 +11,7 @@
#define TEST_BENCHMARKS_CONTAINERS_CONTAINER_BENCHMARKS_H
#include <algorithm>
+#include <concepts>
#include <cstddef>
#include <iterator>
#include <ranges> // for std::from_range
>From 9adfabec82d16b730ff4815528d59bde551e8d8f Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 20 Jan 2025 13:25:46 -0500
Subject: [PATCH 9/9] Fix includes
---
libcxx/test/benchmarks/GenerateInput.h | 1 +
libcxx/test/benchmarks/containers/container_benchmarks.h | 1 -
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/libcxx/test/benchmarks/GenerateInput.h b/libcxx/test/benchmarks/GenerateInput.h
index b659aa35412838..081631a32b21d9 100644
--- a/libcxx/test/benchmarks/GenerateInput.h
+++ b/libcxx/test/benchmarks/GenerateInput.h
@@ -11,6 +11,7 @@
#include <algorithm>
#include <climits>
+#include <concepts>
#include <cstddef>
#include <random>
#include <string>
diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h
index c4a769f19c3173..e24bd767177e87 100644
--- a/libcxx/test/benchmarks/containers/container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/container_benchmarks.h
@@ -11,7 +11,6 @@
#define TEST_BENCHMARKS_CONTAINERS_CONTAINER_BENCHMARKS_H
#include <algorithm>
-#include <concepts>
#include <cstddef>
#include <iterator>
#include <ranges> // for std::from_range
More information about the libcxx-commits
mailing list