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