From 3e70df8634f97bd7b30afe97a233eb4344426d49 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 12 Dec 2024 08:25:15 -0500 Subject: [PATCH 01/10] [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 fed16ba51f995..0000000000000 --- 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 -#include - -#include "benchmark/benchmark.h" - -namespace UtilitiesInternal { -template -auto HaveDataImpl(int) -> decltype((std::declval().data(), std::true_type{})); -template -auto HaveDataImpl(long) -> std::false_type; -template -using HasData = decltype(HaveDataImpl(0)); -} // namespace UtilitiesInternal - -template ::value>* = nullptr> -void DoNotOptimizeData(Container& c) { - benchmark::DoNotOptimize(c.data()); -} - -template ::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 5fc8981619672..0000000000000 --- 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 -#include -#include - -#include "benchmark/benchmark.h" -#include "../Utilities.h" -#include "test_iterators.h" - -namespace ContainerBenchmarks { - -template -void BM_ConstructSize(benchmark::State& st, Container) { - auto size = st.range(0); - for (auto _ : st) { - Container c(size); - DoNotOptimizeData(c); - } -} - -template -void BM_CopyConstruct(benchmark::State& st, Container) { - auto size = st.range(0); - Container c(size); - for (auto _ : st) { - auto v = c; - DoNotOptimizeData(v); - } -} - -template -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 -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 0000000000000..0b65baaaeb777 --- /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 +#include +#include // for std::from_range +#include +#include + +#include "benchmark/benchmark.h" +#include "test_iterators.h" +#include "test_macros.h" + +namespace ContainerBenchmarks { + +template +void DoNotOptimizeData(Container& c) { + if constexpr (requires { c.data(); }) { + benchmark::DoNotOptimize(c.data()); + } else { + benchmark::DoNotOptimize(&c); + } +} + +// +// Sequence container operations +// +template +void BM_ctor_size(benchmark::State& st) { + auto size = st.range(0); + char buffer[sizeof(Container)]; + for (auto _ : st) { + std::construct_at(reinterpret_cast(buffer), size); + benchmark::DoNotOptimize(buffer); + st.PauseTiming(); + std::destroy_at(reinterpret_cast(buffer)); + st.ResumeTiming(); + } +} + +template +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(buffer), size, value); + benchmark::DoNotOptimize(buffer); + st.PauseTiming(); + std::destroy_at(reinterpret_cast(buffer)); + st.ResumeTiming(); + } +} + +template +void BM_ctor_iter_iter(benchmark::State& st) { + using ValueType = typename Container::value_type; + const auto size = st.range(0); + std::vector 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(buffer), begin, end); + benchmark::DoNotOptimize(buffer); + st.PauseTiming(); + std::destroy_at(reinterpret_cast(buffer)); + st.ResumeTiming(); + } +} + +#if TEST_STD_VER >= 23 +template +void BM_ctor_from_range(benchmark::State& st) { + using ValueType = typename Container::value_type; + const auto size = st.range(0); + std::vector in(size); + benchmark::DoNotOptimize(in); + char buffer[sizeof(Container)]; + for (auto _ : st) { + std::construct_at(reinterpret_cast(buffer), std::from_range, in); + benchmark::DoNotOptimize(buffer); + st.PauseTiming(); + std::destroy_at(reinterpret_cast(buffer)); + st.ResumeTiming(); + } +} +#endif + +template +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(buffer), c); + benchmark::DoNotOptimize(buffer); + st.PauseTiming(); + std::destroy_at(reinterpret_cast(buffer)); + st.ResumeTiming(); + } +} + +template +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 +void BM_assign_inputiter(benchmark::State& st) { + using ValueType = typename Container::value_type; + auto size = st.range(0); + std::vector 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 +void BM_insert_start(benchmark::State& st) { + using ValueType = typename Container::value_type; + const int count = st.range(0); + std::vector 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 + requires std::random_access_iterator +void BM_insert_middle(benchmark::State& st) { + using ValueType = typename Container::value_type; + const int count = st.range(0); + std::vector 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 +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 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 +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 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 +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 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 +void BM_erase_start(benchmark::State& st) { + using ValueType = typename Container::value_type; + const int count = st.range(0); + std::vector 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 + requires std::random_access_iterator +void BM_erase_middle(benchmark::State& st) { + using ValueType = typename Container::value_type; + const int count = st.range(0); + std::vector 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 +void sequence_container_benchmarks(std::string container) { + // constructors + benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size)->Arg(1024); + benchmark::RegisterBenchmark(container + "::ctor(size, value_type)", BM_ctor_size_value)->Arg(1024); + benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator)", BM_ctor_iter_iter)->Arg(1024); +#if TEST_STD_VER >= 23 + benchmark::RegisterBenchmark(container + "::ctor(Range)", BM_ctor_from_range)->Arg(1024); +#endif + benchmark::RegisterBenchmark(container + "::ctor(const&)", BM_ctor_copy)->Arg(1024); + + // assignment + benchmark::RegisterBenchmark(container + "::operator=", BM_assignment)->Arg(1024); + benchmark::RegisterBenchmark(container + "::assign(input-iter, input-iter)", BM_assign_inputiter) + ->Arg(1024); + + // insert + benchmark::RegisterBenchmark(container + "::insert(start)", BM_insert_start)->Arg(1024); + if constexpr (std::random_access_iterator) { + benchmark::RegisterBenchmark(container + "::insert(middle)", BM_insert_middle)->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) + ->Arg(514048); + benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (half filled)", + BM_insert_input_iter_with_reserve_no_realloc) + ->Arg(514048); + benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (near full)", + BM_insert_input_iter_with_reserve_near_full) + ->Arg(514048); + } + + // erase + benchmark::RegisterBenchmark(container + "::erase(start)", BM_erase_start)->Arg(1024); + if constexpr (std::random_access_iterator) { + benchmark::RegisterBenchmark(container + "::erase(middle)", BM_erase_middle)->Arg(1024); + } +} + +// +// "Back-insertable" sequence container operations +// +template +void BM_push_back(benchmark::State& st) { + using ValueType = typename Container::value_type; + const int count = st.range(0); + std::vector 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 +void BM_push_back_with_reserve(benchmark::State& st) { + using ValueType = typename Container::value_type; + const int count = st.range(0); + std::vector 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 +void back_insertable_container_benchmarks(std::string container) { + sequence_container_benchmarks(container); + benchmark::RegisterBenchmark(container + "::push_back()", BM_push_back)->Arg(1024); + if constexpr (requires(Container c) { c.reserve(0); }) { + benchmark::RegisterBenchmark(container + "::push_back() (with reserve)", BM_push_back_with_reserve) + ->Arg(1024); + } +} + +// +// Misc operations +// +template +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 +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 +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 +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 +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 +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 +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 +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 +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 7ff1093a9391c..66e54070c6dcf 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 #include +#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"); + ContainerBenchmarks::back_insertable_container_benchmarks>("std::deque"); -using namespace ContainerBenchmarks; - -constexpr std::size_t TestNumInputs = 1024; - -BENCHMARK_CAPTURE(BM_ConstructSize, deque_byte, std::deque{})->Arg(5140480); - -BENCHMARK_CAPTURE(BM_ConstructSizeValue, deque_byte, std::deque{}, 0)->Arg(5140480); - -BENCHMARK_CAPTURE(BM_ConstructIterIter, deque_char, std::deque{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructIterIter, deque_size_t, std::deque{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructIterIter, deque_string, std::deque{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_char, std::deque{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_size_t, std::deque{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructFromRange, deque_string, std::deque{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_int, std::deque{}, getRandomIntegerInputs) - ->Range(TestNumInputs, TestNumInputs * 10); -BENCHMARK_CAPTURE(BM_erase_iter_in_middle, deque_string, std::deque{}, getRandomStringInputs) - ->Range(TestNumInputs, TestNumInputs * 10); - -BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_int, std::deque{}, getRandomIntegerInputs) - ->Range(TestNumInputs, TestNumInputs * 10); -BENCHMARK_CAPTURE(BM_erase_iter_at_start, deque_string, std::deque{}, 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 0000000000000..2212affa02ba4 --- /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 +#include + +#include "container_benchmarks.h" +#include "benchmark/benchmark.h" + +int main(int argc, char** argv) { + ContainerBenchmarks::sequence_container_benchmarks>("std::list"); + ContainerBenchmarks::sequence_container_benchmarks>("std::list"); + + 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 a8448ef5a0cfb..ad8d0feaa0436 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 #include @@ -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 0000000000000..22b62585b74f7 --- /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 +#include + +#include "container_benchmarks.h" +#include "benchmark/benchmark.h" + +int main(int argc, char** argv) { + ContainerBenchmarks::back_insertable_container_benchmarks>("std::vector"); + ContainerBenchmarks::back_insertable_container_benchmarks>("std::vector"); + + 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 1cd754ca7e780..0000000000000 --- 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 -#include -#include -#include -#include -#include -#include -#include - -#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{})->Arg(5140480); - -BENCHMARK_CAPTURE(BM_CopyConstruct, vector_int, std::vector{})->Arg(5140480); - -BENCHMARK_CAPTURE(BM_Assignment, vector_int, std::vector{})->Arg(5140480); - -BENCHMARK_CAPTURE(BM_ConstructSizeValue, vector_byte, std::vector{}, 0)->Arg(5140480); - -BENCHMARK_CAPTURE(BM_ConstructIterIter, vector_char, std::vector{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructIterIter, vector_size_t, std::vector{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructIterIter, vector_string, std::vector{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_char, std::vector{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_size_t, std::vector{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_ConstructFromRange, vector_string, std::vector{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_Pushback_no_grow, vector_int, std::vector{})->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_int, std::vector{}, getRandomIntegerInputs) - ->Range(TestNumInputs, TestNumInputs * 10); -BENCHMARK_CAPTURE(BM_erase_iter_in_middle, vector_string, std::vector{}, getRandomStringInputs) - ->Range(TestNumInputs, TestNumInputs * 10); - -BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_int, std::vector{}, getRandomIntegerInputs) - ->Range(TestNumInputs, TestNumInputs * 10); -BENCHMARK_CAPTURE(BM_erase_iter_at_start, vector_string, std::vector{}, getRandomStringInputs) - ->Range(TestNumInputs, TestNumInputs * 10); - -template -void bm_grow(benchmark::State& state) { - for (auto _ : state) { - std::vector vec; - benchmark::DoNotOptimize(vec); - for (size_t i = 0; i != 2048; ++i) - vec.emplace_back(); - benchmark::DoNotOptimize(vec); - } -} -BENCHMARK(bm_grow); -BENCHMARK(bm_grow); -BENCHMARK(bm_grow>); -BENCHMARK(bm_grow>); - -BENCHMARK_CAPTURE(BM_AssignInputIterIter, vector_int, std::vector{}, getRandomIntegerInputs) - ->Args({TestNumInputs, TestNumInputs}); - -BENCHMARK_CAPTURE( - BM_AssignInputIterIter<32>, vector_string, std::vector{}, getRandomStringInputsWithLength) - ->Args({TestNumInputs, TestNumInputs}); - -BENCHMARK_CAPTURE(BM_AssignInputIterIter<100>, - vector_vector_int, - std::vector>{}, - getRandomIntegerInputsWithLength) - ->Args({TestNumInputs, TestNumInputs}); - -BENCHMARK_CAPTURE(BM_Insert_InputIterIter_NoRealloc, vector_int, std::vector(100, 1), getRandomIntegerInputs) - ->Arg(514048); -BENCHMARK_CAPTURE( - BM_Insert_InputIterIter_Realloc_HalfFilled, vector_int, std::vector{}, getRandomIntegerInputs) - ->Arg(514048); -BENCHMARK_CAPTURE(BM_Insert_InputIterIter_Realloc_NearFull, vector_int, std::vector{}, getRandomIntegerInputs) - ->Arg(514048); -BENCHMARK_CAPTURE( - BM_Insert_InputIterIter_Realloc_HalfFilled, vector_string, std::vector{}, getSSORandomStringInputs) - ->Arg(514048); -BENCHMARK_CAPTURE( - BM_Insert_InputIterIter_Realloc_NearFull, vector_string, std::vector{}, getSSORandomStringInputs) - ->Arg(514048); - -BENCHMARK_MAIN(); From 78968216fd6bf99fcc0bce25ed646bd779f22a7b Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Tue, 14 Jan 2025 19:06:08 -0500 Subject: [PATCH 02/10] 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 6d5c5167e91ed..c2b034162671c 100644 --- a/libcxx/test/benchmarks/GenerateInput.h +++ b/libcxx/test/benchmarks/GenerateInput.h @@ -171,4 +171,31 @@ inline std::vector getRandomCStringInputs(std::size_t N) { return cinputs; } +template +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 + requires std::integral +struct Generate { + static T arbitrary() { return 42; } + static T cheap() { return 42; } + static T expensive() { return 42; } +}; + +template <> +struct Generate { + 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 0b65baaaeb777..1bdfc42115bf0 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 #include #include #include // 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 -void BM_ctor_size_value(benchmark::State& st) { +template +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 -void BM_ctor_iter_iter(benchmark::State& st) { +template +void BM_ctor_iter_iter(benchmark::State& st, Generator gen) { using ValueType = typename Container::value_type; const auto size = st.range(0); - std::vector in(size); + std::vector 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 void sequence_container_benchmarks(std::string container) { + using ValueType = typename Container::value_type; + auto cheap = [] { return Generate::cheap(); }; + auto expensive = [] { return Generate::expensive(); }; + // constructors benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size)->Arg(1024); - benchmark::RegisterBenchmark(container + "::ctor(size, value_type)", BM_ctor_size_value)->Arg(1024); - benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator)", BM_ctor_iter_iter)->Arg(1024); + benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (cheap elements)", [=](auto& st) { + BM_ctor_size_value(st, cheap); + })->Arg(1024); + benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (expensive elements)", [=](auto& st) { + BM_ctor_size_value(st, expensive); + })->Arg(1024); + benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (cheap elements)", [=](auto& st) { + BM_ctor_iter_iter(st, cheap); + })->Arg(1024); + benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (expensive elements)", [=](auto& st) { + BM_ctor_iter_iter(st, expensive); + })->Arg(1024); #if TEST_STD_VER >= 23 benchmark::RegisterBenchmark(container + "::ctor(Range)", BM_ctor_from_range)->Arg(1024); #endif From 238244f09b85412a92639f6b0a8d696aeabf6923 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 16 Jan 2025 12:45:46 -0500 Subject: [PATCH 03/10] 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 c2b034162671c..b659aa3541283 100644 --- a/libcxx/test/benchmarks/GenerateInput.h +++ b/libcxx/test/benchmarks/GenerateInput.h @@ -195,7 +195,7 @@ template <> struct Generate { 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 1bdfc42115bf0..fde148bff9c5d 100644 --- a/libcxx/test/benchmarks/containers/container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/container_benchmarks.h @@ -15,6 +15,7 @@ #include #include // for std::from_range #include +#include #include #include "benchmark/benchmark.h" @@ -39,13 +40,10 @@ void DoNotOptimizeData(Container& c) { template void BM_ctor_size(benchmark::State& st) { auto size = st.range(0); - char buffer[sizeof(Container)]; + for (auto _ : st) { - std::construct_at(reinterpret_cast(buffer), size); - benchmark::DoNotOptimize(buffer); - st.PauseTiming(); - std::destroy_at(reinterpret_cast(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(buffer), size, value); - benchmark::DoNotOptimize(buffer); - st.PauseTiming(); - std::destroy_at(reinterpret_cast(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(buffer), begin, end); - benchmark::DoNotOptimize(buffer); - st.PauseTiming(); - std::destroy_at(reinterpret_cast(buffer)); - st.ResumeTiming(); + Container c(begin, end); // we assume the destructor doesn't dominate the benchmark + DoNotOptimizeData(c); } } #if TEST_STD_VER >= 23 -template -void BM_ctor_from_range(benchmark::State& st) { +template +void BM_ctor_from_range(benchmark::State& st, Generator gen) { using ValueType = typename Container::value_type; const auto size = st.range(0); - std::vector in(size); + std::vector 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(buffer), std::from_range, in); - benchmark::DoNotOptimize(buffer); - st.PauseTiming(); - std::destroy_at(reinterpret_cast(buffer)); - st.ResumeTiming(); + Container c(std::from_range, in); // we assume the destructor doesn't dominate the benchmark + DoNotOptimizeData(c); } } #endif -template -void BM_ctor_copy(benchmark::State& st) { +template +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(buffer), c); - benchmark::DoNotOptimize(buffer); - st.PauseTiming(); - std::destroy_at(reinterpret_cast(buffer)); - st.ResumeTiming(); + Container c(in); // we assume the destructor doesn't dominate the benchmark + DoNotOptimizeData(c); + DoNotOptimizeData(in); } } -template -void BM_assignment(benchmark::State& st) { +template +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 -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 +void BM_assign_input_iter_full(benchmark::State& st, Generator gen) { using ValueType = typename Container::value_type; auto size = st.range(0); - std::vector inputs(size); - Container c(inputs.begin(), inputs.end()); - DoNotOptimizeData(c); - DoNotOptimizeData(inputs); - ValueType* first = inputs.data(); - ValueType* last = inputs.data() + inputs.size(); - + std::vector 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& 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 -void BM_insert_start(benchmark::State& st) { +template +void BM_insert_start(benchmark::State& st, Generator gen) { using ValueType = typename Container::value_type; - const int count = st.range(0); - std::vector inputs(count); - Container c(inputs.begin(), inputs.end()); + const int size = st.range(0); + std::vector 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 +template requires std::random_access_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 inputs(count); - Container c(inputs.begin(), inputs.end()); + const int size = st.range(0); + std::vector 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 -void BM_insert_input_iter_with_reserve_no_realloc(benchmark::State& st) { +template +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 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 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 -void BM_insert_input_iter_with_reserve_half_filled(benchmark::State& st) { +template +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 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 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 -void BM_insert_input_iter_with_reserve_near_full(benchmark::State& st) { +template +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 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 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 -void BM_erase_start(benchmark::State& st) { +template +void BM_erase_start(benchmark::State& st, Generator gen) { using ValueType = typename Container::value_type; - const int count = st.range(0); - std::vector inputs(count); - Container c(inputs.begin(), inputs.end()); + const int size = st.range(0); + std::vector 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 +template requires std::random_access_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 inputs(count); - Container c(inputs.begin(), inputs.end()); + const int size = st.range(0); + std::vector 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 -void sequence_container_benchmarks(std::string container) { - using ValueType = typename Container::value_type; - auto cheap = [] { return Generate::cheap(); }; - auto expensive = [] { return Generate::expensive(); }; - - // constructors - benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size)->Arg(1024); - benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (cheap elements)", [=](auto& st) { - BM_ctor_size_value(st, cheap); - })->Arg(1024); - benchmark::RegisterBenchmark(container + "::ctor(size, value_type) (expensive elements)", [=](auto& st) { - BM_ctor_size_value(st, expensive); - })->Arg(1024); - benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (cheap elements)", [=](auto& st) { - BM_ctor_iter_iter(st, cheap); - })->Arg(1024); - benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator) (expensive elements)", [=](auto& st) { - BM_ctor_iter_iter(st, expensive); - })->Arg(1024); -#if TEST_STD_VER >= 23 - benchmark::RegisterBenchmark(container + "::ctor(Range)", BM_ctor_from_range)->Arg(1024); -#endif - benchmark::RegisterBenchmark(container + "::ctor(const&)", BM_ctor_copy)->Arg(1024); - - // assignment - benchmark::RegisterBenchmark(container + "::operator=", BM_assignment)->Arg(1024); - benchmark::RegisterBenchmark(container + "::assign(input-iter, input-iter)", BM_assign_inputiter) - ->Arg(1024); - - // insert - benchmark::RegisterBenchmark(container + "::insert(start)", BM_insert_start)->Arg(1024); - if constexpr (std::random_access_iterator) { - benchmark::RegisterBenchmark(container + "::insert(middle)", BM_insert_middle)->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) - ->Arg(514048); - benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (half filled)", - BM_insert_input_iter_with_reserve_no_realloc) - ->Arg(514048); - benchmark::RegisterBenchmark(container + "::insert(input-iter, input-iter) (near full)", - BM_insert_input_iter_with_reserve_near_full) - ->Arg(514048); - } - - // erase - benchmark::RegisterBenchmark(container + "::erase(start)", BM_erase_start)->Arg(1024); - if constexpr (std::random_access_iterator) { - benchmark::RegisterBenchmark(container + "::erase(middle)", BM_erase_middle)->Arg(1024); - } -} - -// -// "Back-insertable" sequence container operations -// -template -void BM_push_back(benchmark::State& st) { +template +void BM_push_back(benchmark::State& st, Generator gen) { using ValueType = typename Container::value_type; - const int count = st.range(0); - std::vector inputs(count); - benchmark::DoNotOptimize(inputs); + const int size = st.range(0); + std::vector 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 -void BM_push_back_with_reserve(benchmark::State& st) { +template +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 inputs(count); - benchmark::DoNotOptimize(inputs); + const int size = st.range(0); + std::vector 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 -void back_insertable_container_benchmarks(std::string container) { - sequence_container_benchmarks(container); - benchmark::RegisterBenchmark(container + "::push_back()", BM_push_back)->Arg(1024); - if constexpr (requires(Container c) { c.reserve(0); }) { - benchmark::RegisterBenchmark(container + "::push_back() (with reserve)", BM_push_back_with_reserve) +void sequence_container_benchmarks(std::string container) { + using ValueType = typename Container::value_type; + + using Generator = ValueType (*)(); + Generator cheap = [] { return Generate::cheap(); }; + Generator expensive = [] { return Generate::expensive(); }; + auto tostr = [&](Generator gen) { return gen == cheap ? " (cheap elements)" : " (expensive elements)"; }; + std::vector generators; + generators.push_back(cheap); + if constexpr (!std::is_integral_v) { + generators.push_back(expensive); + } + + // constructors + benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size)->Arg(1024); + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::ctor(size, value_type)" + tostr(gen), [=](auto& st) { + BM_ctor_size_value(st, gen); + })->Arg(1024); + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::ctor(Iterator, Iterator)" + tostr(gen), [=](auto& st) { + BM_ctor_iter_iter(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(st, gen); + })->Arg(1024); +#endif + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::ctor(const&)" + tostr(gen), [=](auto& st) { + BM_ctor_copy(st, gen); + })->Arg(1024); + + // assignment + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::operator=(const&)" + tostr(gen), [=](auto& st) { + BM_assignment(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(st, gen); }) ->Arg(1024); + + // insert + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::insert(begin)" + tostr(gen), [=](auto& st) { + BM_insert_start(st, gen); + })->Arg(1024); + if constexpr (std::random_access_iterator) { + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::insert(middle)" + tostr(gen), [=](auto& st) { + BM_insert_middle(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(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(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(st, gen); }) + ->Arg(1024); + } + + // erase + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::erase(start)" + tostr(gen), [=](auto& st) { + BM_erase_start(st, gen); + })->Arg(1024); + if constexpr (std::random_access_iterator) { + for (auto gen : generators) + benchmark::RegisterBenchmark(container + "::erase(middle)" + tostr(gen), [=](auto& st) { + BM_erase_middle(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(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(st, gen); + })->Arg(1024); + } } } diff --git a/libcxx/test/benchmarks/containers/deque.bench.cpp b/libcxx/test/benchmarks/containers/deque.bench.cpp index 66e54070c6dcf..6a650fa4dce2a 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"); - ContainerBenchmarks::back_insertable_container_benchmarks>("std::deque"); + ContainerBenchmarks::sequence_container_benchmarks>("std::deque"); + ContainerBenchmarks::sequence_container_benchmarks>("std::deque"); 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 22b62585b74f7..eef23d2981642 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"); - ContainerBenchmarks::back_insertable_container_benchmarks>("std::vector"); + ContainerBenchmarks::sequence_container_benchmarks>("std::vector"); + ContainerBenchmarks::sequence_container_benchmarks>("std::vector"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); From d7c06795a124f9badddc0a11898ad1a03e58b12f Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 16 Jan 2025 16:55:10 -0500 Subject: [PATCH 04/10] 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 fde148bff9c5d..3daeddc9f5a71 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 -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 in; @@ -201,7 +201,7 @@ void BM_insert_middle(benchmark::State& st, Generator gen) { } template -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 in; @@ -226,7 +226,7 @@ void BM_insert_start_input_iter_with_reserve_no_realloc(benchmark::State& st, Ge } template -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 in; @@ -249,7 +249,7 @@ void BM_insert_start_input_iter_with_reserve_half_filled(benchmark::State& st, G } template -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 in; @@ -272,7 +272,7 @@ void BM_insert_start_input_iter_with_reserve_near_full(benchmark::State& st, Gen } template -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 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(st, gen); + BM_insert_begin(st, gen); })->Arg(1024); if constexpr (std::random_access_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(st, gen); }) + container + "::insert(begin, input-iter, input-iter) (no realloc)" + tostr(gen), + [=](auto& st) { BM_insert_begin_input_iter_with_reserve_no_realloc(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(st, gen); }) + container + "::insert(begin, input-iter, input-iter) (half filled)" + tostr(gen), + [=](auto& st) { BM_insert_begin_input_iter_with_reserve_half_filled(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(st, gen); }) + container + "::insert(begin, input-iter, input-iter) (near full)" + tostr(gen), + [=](auto& st) { BM_insert_begin_input_iter_with_reserve_near_full(st, gen); }) ->Arg(1024); } // erase for (auto gen : generators) - benchmark::RegisterBenchmark(container + "::erase(start)" + tostr(gen), [=](auto& st) { - BM_erase_start(st, gen); + benchmark::RegisterBenchmark(container + "::erase(begin)" + tostr(gen), [=](auto& st) { + BM_erase_begin(st, gen); })->Arg(1024); if constexpr (std::random_access_iterator) { for (auto gen : generators) From 345ac670526baccf789e84c1d59875ebb8439919 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 16 Jan 2025 17:13:51 -0500 Subject: [PATCH 05/10] 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 3daeddc9f5a71..4d9ff1bf735e3 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 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 -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 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 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(st, gen); }) + [=](auto& st) { BM_insert_begin_input_iter_with_reserve_almost_no_realloc(st, gen); }) ->Arg(1024); for (auto gen : generators) benchmark::RegisterBenchmark( From 93d9a3d456e112a6379014f176cb187296a21888 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 16 Jan 2025 17:37:45 -0500 Subject: [PATCH 06/10] 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 4d9ff1bf735e3..7f75ab78bc344 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)->Arg(1024); + if constexpr (std::is_constructible_v) { + // not all containers provide this one + benchmark::RegisterBenchmark(container + "::ctor(size)", BM_ctor_size)->Arg(1024); + } for (auto gen : generators) benchmark::RegisterBenchmark(container + "::ctor(size, value_type)" + tostr(gen), [=](auto& st) { BM_ctor_size_value(st, gen); From 893b9048a664fab9f8325c40305819871fa14833 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Fri, 17 Jan 2025 09:46:23 -0500 Subject: [PATCH 07/10] 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 7f75ab78bc344..e24bd767177e8 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 Date: Fri, 17 Jan 2025 16:18:26 -0500 Subject: [PATCH 08/10] 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 e24bd767177e8..c4a769f19c317 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 +#include #include #include #include // for std::from_range From 9adfabec82d16b730ff4815528d59bde551e8d8f Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Mon, 20 Jan 2025 13:25:46 -0500 Subject: [PATCH 09/10] 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 b659aa3541283..081631a32b21d 100644 --- a/libcxx/test/benchmarks/GenerateInput.h +++ b/libcxx/test/benchmarks/GenerateInput.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include diff --git a/libcxx/test/benchmarks/containers/container_benchmarks.h b/libcxx/test/benchmarks/containers/container_benchmarks.h index c4a769f19c317..e24bd767177e8 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 -#include #include #include #include // for std::from_range From 4f3f35e75096591fe362ea7ad5fddef0c790c789 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Tue, 28 Jan 2025 16:49:55 -0500 Subject: [PATCH 10/10] Add missing UNSUPPORTED annotations --- .../benchmarks/algorithms/algorithms.partition_point.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/lower_bound.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/make_heap.bench.cpp | 2 +- .../benchmarks/algorithms/make_heap_then_sort_heap.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/pop_heap.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/pstl.stable_sort.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/push_heap.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/set_intersection.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/sort.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/sort_heap.bench.cpp | 2 +- libcxx/test/benchmarks/algorithms/stable_sort.bench.cpp | 2 +- libcxx/test/benchmarks/containers/string.bench.cpp | 2 +- libcxx/test/benchmarks/filesystem.bench.cpp | 2 +- libcxx/test/benchmarks/hash.bench.cpp | 2 +- libcxx/test/benchmarks/variant_visit_1.bench.cpp | 2 +- libcxx/test/benchmarks/variant_visit_2.bench.cpp | 2 +- libcxx/test/benchmarks/variant_visit_3.bench.cpp | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp b/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp index 0777acbafb5cc..e0bd7e36f78ad 100644 --- a/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/algorithms.partition_point.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include #include diff --git a/libcxx/test/benchmarks/algorithms/lower_bound.bench.cpp b/libcxx/test/benchmarks/algorithms/lower_bound.bench.cpp index d9d57969df67a..31fb3597241fc 100644 --- a/libcxx/test/benchmarks/algorithms/lower_bound.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/lower_bound.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include #include diff --git a/libcxx/test/benchmarks/algorithms/make_heap.bench.cpp b/libcxx/test/benchmarks/algorithms/make_heap.bench.cpp index b7320e17c3e50..64d559620c512 100644 --- a/libcxx/test/benchmarks/algorithms/make_heap.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/make_heap.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/algorithms/make_heap_then_sort_heap.bench.cpp b/libcxx/test/benchmarks/algorithms/make_heap_then_sort_heap.bench.cpp index 5991d2846aee4..c6dc136be3ac4 100644 --- a/libcxx/test/benchmarks/algorithms/make_heap_then_sort_heap.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/make_heap_then_sort_heap.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/algorithms/pop_heap.bench.cpp b/libcxx/test/benchmarks/algorithms/pop_heap.bench.cpp index 5fef52284239d..e4b96a0ae48c7 100644 --- a/libcxx/test/benchmarks/algorithms/pop_heap.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/pop_heap.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/algorithms/pstl.stable_sort.bench.cpp b/libcxx/test/benchmarks/algorithms/pstl.stable_sort.bench.cpp index 10254ac12cf56..a385185ec7fe5 100644 --- a/libcxx/test/benchmarks/algorithms/pstl.stable_sort.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/pstl.stable_sort.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 // UNSUPPORTED: libcpp-has-no-incomplete-pstl #include diff --git a/libcxx/test/benchmarks/algorithms/push_heap.bench.cpp b/libcxx/test/benchmarks/algorithms/push_heap.bench.cpp index 89d8122bd1dbe..7dfa0285348bd 100644 --- a/libcxx/test/benchmarks/algorithms/push_heap.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/push_heap.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/algorithms/set_intersection.bench.cpp b/libcxx/test/benchmarks/algorithms/set_intersection.bench.cpp index 9bde4bb29dc22..40292179781ee 100644 --- a/libcxx/test/benchmarks/algorithms/set_intersection.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/set_intersection.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include #include diff --git a/libcxx/test/benchmarks/algorithms/sort.bench.cpp b/libcxx/test/benchmarks/algorithms/sort.bench.cpp index 899272e34795f..7f3ce6ff7a07e 100644 --- a/libcxx/test/benchmarks/algorithms/sort.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/sort.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/algorithms/sort_heap.bench.cpp b/libcxx/test/benchmarks/algorithms/sort_heap.bench.cpp index ee4b6bfc7387b..1ce9f1a6df9af 100644 --- a/libcxx/test/benchmarks/algorithms/sort_heap.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/sort_heap.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/algorithms/stable_sort.bench.cpp b/libcxx/test/benchmarks/algorithms/stable_sort.bench.cpp index c68f73838c319..26e8de935f5c5 100644 --- a/libcxx/test/benchmarks/algorithms/stable_sort.bench.cpp +++ b/libcxx/test/benchmarks/algorithms/stable_sort.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/containers/string.bench.cpp b/libcxx/test/benchmarks/containers/string.bench.cpp index 0b62c87acf7a2..aeff6ad6f6333 100644 --- a/libcxx/test/benchmarks/containers/string.bench.cpp +++ b/libcxx/test/benchmarks/containers/string.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include #include diff --git a/libcxx/test/benchmarks/filesystem.bench.cpp b/libcxx/test/benchmarks/filesystem.bench.cpp index 83a87c86d3de0..dc6b0ac537f7e 100644 --- a/libcxx/test/benchmarks/filesystem.bench.cpp +++ b/libcxx/test/benchmarks/filesystem.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include diff --git a/libcxx/test/benchmarks/hash.bench.cpp b/libcxx/test/benchmarks/hash.bench.cpp index 1e1a0f36ec116..ca958765dc210 100644 --- a/libcxx/test/benchmarks/hash.bench.cpp +++ b/libcxx/test/benchmarks/hash.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include #include diff --git a/libcxx/test/benchmarks/variant_visit_1.bench.cpp b/libcxx/test/benchmarks/variant_visit_1.bench.cpp index 42b22aabaee04..f1b702530bed3 100644 --- a/libcxx/test/benchmarks/variant_visit_1.bench.cpp +++ b/libcxx/test/benchmarks/variant_visit_1.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include "benchmark/benchmark.h" diff --git a/libcxx/test/benchmarks/variant_visit_2.bench.cpp b/libcxx/test/benchmarks/variant_visit_2.bench.cpp index 328048cabc443..7dd8d02b358be 100644 --- a/libcxx/test/benchmarks/variant_visit_2.bench.cpp +++ b/libcxx/test/benchmarks/variant_visit_2.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include "benchmark/benchmark.h" diff --git a/libcxx/test/benchmarks/variant_visit_3.bench.cpp b/libcxx/test/benchmarks/variant_visit_3.bench.cpp index 40f8c1b5fa262..0fe42b0d8e009 100644 --- a/libcxx/test/benchmarks/variant_visit_3.bench.cpp +++ b/libcxx/test/benchmarks/variant_visit_3.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 #include "benchmark/benchmark.h"