From 8e1b692d1d59ba5f5013d0f6a7b4aa3bacbd346c Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Fri, 17 Jan 2025 10:42:36 -0500 Subject: [PATCH 01/16] [libc++] Implement generic associative container benchmarks This patch implements generic associative container benchmarks for containers with unique keys. In doing so, it replaces the existing std::map benchmarks which were based on the cartesian product infrastructure and were too slow to execute. These new benchmarks aim to strike a balance between exhaustive coverage of all operations in the most interesting case, while executing fairly rapidly (~40s on my machine). --- libcxx/test/benchmarks/GenerateInput.h | 5 + .../associative_container_benchmarks.h | 536 ++++++++++ .../benchmarks/containers/flat_map.bench.cpp | 25 + .../test/benchmarks/containers/map.bench.cpp | 940 +----------------- .../test/benchmarks/containers/set.bench.cpp | 23 + 5 files changed, 597 insertions(+), 932 deletions(-) create mode 100644 libcxx/test/benchmarks/containers/associative_container_benchmarks.h create mode 100644 libcxx/test/benchmarks/containers/flat_map.bench.cpp create mode 100644 libcxx/test/benchmarks/containers/set.bench.cpp diff --git a/libcxx/test/benchmarks/GenerateInput.h b/libcxx/test/benchmarks/GenerateInput.h index 081631a32b21d..c87fd69162e9d 100644 --- a/libcxx/test/benchmarks/GenerateInput.h +++ b/libcxx/test/benchmarks/GenerateInput.h @@ -190,6 +190,7 @@ struct Generate { static T arbitrary() { return 42; } static T cheap() { return 42; } static T expensive() { return 42; } + static T random() { return getRandomInteger(std::numeric_limits::min(), std::numeric_limits::max()); } }; template <> @@ -197,6 +198,10 @@ struct Generate { static std::string arbitrary() { return "hello world"; } static std::string cheap() { return "small"; } static std::string expensive() { return std::string(256, 'x'); } + static std::string random() { + auto length = getRandomInteger(1, 1024); + return getRandomString(length); + } }; #endif // BENCHMARK_GENERATE_INPUT_H diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h new file mode 100644 index 0000000000000..3bddc0c73b54e --- /dev/null +++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h @@ -0,0 +1,536 @@ +//===----------------------------------------------------------------------===// +// +// 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_ASSOCIATIVE_CONTAINER_BENCHMARKS_H +#define TEST_BENCHMARKS_CONTAINERS_ASSOCIATIVE_CONTAINER_BENCHMARKS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark/benchmark.h" +#include "../GenerateInput.h" +#include "test_macros.h" + +namespace support { + +template +struct adapt_operations; + +template +struct adapt_operations> { + using ValueType = typename std::set::value_type; + using KeyType = typename std::set::key_type; + static ValueType value_from_key(KeyType const& k) { return k; } + static KeyType key_from_value(ValueType const& value) { return value; } +}; + +template +struct adapt_operations> { + using ValueType = typename std::map::value_type; + using KeyType = typename std::map::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } +}; + +#if TEST_STD_VER >= 26 +template +struct adapt_operations> { + using ValueType = typename std::map::value_type; + using KeyType = typename std::map::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } +}; +#endif + +template +void associative_container_benchmarks(std::string container) { + using Key = typename Container::key_type; + using Value = typename Container::value_type; + + auto generate_unique_keys = [=](std::size_t n) { + std::set keys; + while (keys.size() < n) { + Key k = Generate::random(); + keys.insert(k); + } + return std::vector(keys.begin(), keys.end()); + }; + + auto add_dummy_mapped_type = [](std::vector const& keys) { + std::vector kv; + for (Key const& k : keys) + kv.push_back(adapt_operations::value_from_key(k)); + return kv; + }; + + auto get_key = [](Value const& v) { return adapt_operations::key_from_value(v); }; + + // 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 = 10; + + struct ScratchSpace { + char storage[sizeof(Container)]; + }; + + ///////////////////////// + // Constructors + ///////////////////////// + benchmark::RegisterBenchmark(container + "::ctor(const&)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Container src(in.begin(), in.end()); + ScratchSpace c[BatchSize]; + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + new (c + i) Container(src); + benchmark::DoNotOptimize(c + i); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + reinterpret_cast(c + i)->~Container(); + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (unsorted sequence)", [=](auto& st) { + const std::size_t size = st.range(0); + std::mt19937 randomness; + std::vector keys = generate_unique_keys(size); + std::shuffle(keys.begin(), keys.end(), randomness); + std::vector in = add_dummy_mapped_type(keys); + ScratchSpace c[BatchSize]; + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + new (c + i) Container(in.begin(), in.end()); + benchmark::DoNotOptimize(c + i); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + reinterpret_cast(c + i)->~Container(); + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (sorted sequence)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector keys = generate_unique_keys(size); + std::sort(keys.begin(), keys.end()); + std::vector in = add_dummy_mapped_type(keys); + ScratchSpace c[BatchSize]; + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + new (c + i) Container(in.begin(), in.end()); + benchmark::DoNotOptimize(c + i); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + reinterpret_cast(c + i)->~Container(); + } + st.ResumeTiming(); + } + })->Arg(1024); + + ///////////////////////// + // Assignment + ///////////////////////// + benchmark::RegisterBenchmark(container + "::operator=(const&)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Container src(in.begin(), in.end()); + Container c[BatchSize]; + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i] = src; + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].clear(); + } + st.ResumeTiming(); + } + })->Arg(1024); + + ///////////////////////// + // Insertion + ///////////////////////// + benchmark::RegisterBenchmark(container + "::insert(value) (already present)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Value to_insert = in[in.size() / 2]; // pick any existing value + std::vector c(BatchSize, Container(in.begin(), in.end())); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(to_insert); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + // There is no cleanup to do, since associative containers don't insert + // if the key is already present. + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::insert(value) (new value)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + Value to_insert = in.back(); + in.pop_back(); + std::vector c(BatchSize, Container(in.begin(), in.end())); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(to_insert); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(get_key(to_insert)); + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + Value to_insert = in.back(); + in.pop_back(); + + std::vector c(BatchSize, Container(in.begin(), in.end())); + typename Container::iterator hints[BatchSize]; + for (std::size_t i = 0; i != BatchSize; ++i) { + hints[i] = c[i].lower_bound(get_key(to_insert)); + } + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(hints[i], to_insert); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(get_key(to_insert)); + hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + Value to_insert = in.back(); + in.pop_back(); + std::vector c(BatchSize, Container(in.begin(), in.end())); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(c[i].begin(), to_insert); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(get_key(to_insert)); + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (all new keys)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size + (size / 10))); + + // Populate a container with a small number of elements, that's what containers will start with. + std::vector small; + for (std::size_t i = 0; i != (size / 10); ++i) { + small.push_back(in.back()); + in.pop_back(); + } + Container c(small.begin(), small.end()); + + for (auto _ : st) { + c.insert(in.begin(), in.end()); + benchmark::DoNotOptimize(c); + benchmark::ClobberMemory(); + + st.PauseTiming(); + c = Container(small.begin(), small.end()); + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (half new keys)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + + // Populate a container that already contains half the elements we'll try inserting, + // that's what our container will start with. + std::vector small; + for (std::size_t i = 0; i != size / 2; ++i) { + small.push_back(in.at(i * 2)); + } + Container c(small.begin(), small.end()); + + for (auto _ : st) { + c.insert(in.begin(), in.end()); + benchmark::DoNotOptimize(c); + benchmark::ClobberMemory(); + + st.PauseTiming(); + c = Container(small.begin(), small.end()); + st.ResumeTiming(); + } + })->Arg(1024); + + ///////////////////////// + // Erasure + ///////////////////////// + benchmark::RegisterBenchmark(container + "::erase(key) (existent)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Value element = in[in.size() / 2]; // pick any element + std::vector c(BatchSize, Container(in.begin(), in.end())); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(get_key(element)); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(element); + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::erase(key) (non-existent)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + Value element = in.back(); + in.pop_back(); + Container c(in.begin(), in.end()); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c.erase(get_key(element)); + benchmark::DoNotOptimize(c); + benchmark::ClobberMemory(); + } + + // no cleanup required because we erased a non-existent element + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::erase(iterator)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Value element = in[in.size() / 2]; // pick any element + + std::vector c; + std::vector iterators; + for (std::size_t i = 0; i != BatchSize; ++i) { + c.push_back(Container(in.begin(), in.end())); + iterators.push_back(c[i].find(get_key(element))); + } + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(iterators[i]); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + iterators[i] = c[i].insert(element).first; + } + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::erase(iterator, iterator) (erase half the container)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Container c(in.begin(), in.end()); + + auto first = std::next(c.begin(), c.size() / 4); + auto last = std::next(c.begin(), 3 * (c.size() / 4)); + for (auto _ : st) { + c.erase(first, last); + benchmark::DoNotOptimize(c); + benchmark::ClobberMemory(); + + st.PauseTiming(); + c = Container(in.begin(), in.end()); + first = std::next(c.begin(), c.size() / 4); + last = std::next(c.begin(), 3 * (c.size() / 4)); + st.ResumeTiming(); + } + })->Arg(1024); + + benchmark::RegisterBenchmark(container + "::clear()", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Container c(in.begin(), in.end()); + + for (auto _ : st) { + c.clear(); + benchmark::DoNotOptimize(c); + benchmark::ClobberMemory(); + + st.PauseTiming(); + c = Container(in.begin(), in.end()); + st.ResumeTiming(); + } + })->Arg(1024); + + ///////////////////////// + // Query + ///////////////////////// + auto bench_with_existent_key = [=](auto func) { + return [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + Value element = in[in.size() / 2]; // pick any element + Container c(in.begin(), in.end()); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + auto result = func(c, element); + benchmark::DoNotOptimize(c); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + } + }; + }; + + auto bench_with_nonexistent_key = [=](auto func) { + return [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + Value element = in.back(); + in.pop_back(); + Container c(in.begin(), in.end()); + + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + auto result = func(c, element); + benchmark::DoNotOptimize(c); + benchmark::DoNotOptimize(result); + benchmark::ClobberMemory(); + } + } + }; + }; + + benchmark::RegisterBenchmark( + container + "::find(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::find(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::count(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::count(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::contains(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { return c.contains(get_key(element)); })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::contains(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.contains(get_key(element)); + })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::lower_bound(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { + return c.lower_bound(get_key(element)); + })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::lower_bound(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.lower_bound(get_key(element)); + })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::upper_bound(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { + return c.upper_bound(get_key(element)); + })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::upper_bound(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.upper_bound(get_key(element)); + })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::equal_range(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { + return c.equal_range(get_key(element)); + })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::equal_range(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.equal_range(get_key(element)); + })) + ->Arg(1024); +} + +} // namespace support + +#endif // TEST_BENCHMARKS_CONTAINERS_ASSOCIATIVE_CONTAINER_BENCHMARKS_H diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp new file mode 100644 index 0000000000000..39971c9319e6c --- /dev/null +++ b/libcxx/test/benchmarks/containers/flat_map.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, c++20, c++23 + +#include +#include + +#include "associative_container_benchmarks.h" +#include "benchmark/benchmark.h" + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::flat_map"); + support::associative_container_benchmarks>("std::flat_map"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/map.bench.cpp index e37c7d8d55163..5c5ba7cc9d3db 100644 --- a/libcxx/test/benchmarks/containers/map.bench.cpp +++ b/libcxx/test/benchmarks/containers/map.bench.cpp @@ -6,944 +6,20 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14 +// UNSUPPORTED: c++03, c++11, c++14, c++17 -#include -#include #include -#include -#include +#include -#include "../CartesianBenchmarks.h" +#include "associative_container_benchmarks.h" #include "benchmark/benchmark.h" -#include "test_macros.h" - -// When VALIDATE is defined the benchmark will run to validate the benchmarks. -// The time taken by several operations depend on whether or not an element -// exists. To avoid errors in the benchmark these operations have a validation -// mode to test the benchmark. Since they are not meant to be benchmarked the -// number of sizes tested is limited to 1. -// #define VALIDATE - -namespace { - -enum class Mode { Hit, Miss }; - -struct AllModes : EnumValuesAsTuple { - static constexpr const char* Names[] = {"ExistingElement", "NewElement"}; -}; - -// The positions of the hints to pick: -// - Begin picks the first item. The item cannot be put before this element. -// - Thrid picks the third item. This is just an element with a valid entry -// before and after it. -// - Correct contains the correct hint. -// - End contains a hint to the end of the map. -enum class Hint { Begin, Third, Correct, End }; -struct AllHints : EnumValuesAsTuple { - static constexpr const char* Names[] = {"Begin", "Third", "Correct", "End"}; -}; - -enum class Order { Sorted, Random }; -struct AllOrders : EnumValuesAsTuple { - static constexpr const char* Names[] = {"Sorted", "Random"}; -}; - -struct TestSets { - std::vector Keys; - std::vector > Maps; - std::vector::const_iterator> > Hints; -}; - -enum class Shuffle { None, Keys, Hints }; - -TestSets makeTestingSets(size_t MapSize, Mode mode, Shuffle shuffle, size_t max_maps) { - /* - * The shuffle does not retain the random number generator to use the same - * set of random numbers for every iteration. - */ - TestSets R; - - int MapCount = std::min(max_maps, 1000000 / MapSize); - - for (uint64_t I = 0; I < MapSize; ++I) { - R.Keys.push_back(mode == Mode::Hit ? 2 * I + 2 : 2 * I + 1); - } - if (shuffle == Shuffle::Keys) - std::shuffle(R.Keys.begin(), R.Keys.end(), std::mt19937()); - - for (int M = 0; M < MapCount; ++M) { - auto& map = R.Maps.emplace_back(); - auto& hints = R.Hints.emplace_back(); - for (uint64_t I = 0; I < MapSize; ++I) { - hints.push_back(map.insert(std::make_pair(2 * I + 2, 0)).first); - } - if (shuffle == Shuffle::Hints) - std::shuffle(hints.begin(), hints.end(), std::mt19937()); - } - - return R; -} - -struct Base { - size_t MapSize; - Base(size_t T) : MapSize(T) {} - - std::string baseName() const { return "_MapSize=" + std::to_string(MapSize); } -}; - -//*******************************************************************| -// Member functions | -//*******************************************************************| - -struct ConstructorDefault { - void run(benchmark::State& State) const { - for (auto _ : State) { - benchmark::DoNotOptimize(std::map()); - } - } - - std::string name() const { return "BM_ConstructorDefault"; } -}; - -struct ConstructorIterator : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { -#ifndef VALIDATE - benchmark::DoNotOptimize(std::map(Map.begin(), Map.end())); -#else - std::map M{Map.begin(), Map.end()}; - if (M != Map) - State.SkipWithError("Map copy not identical"); -#endif - } - } - - std::string name() const { return "BM_ConstructorIterator" + baseName(); } -}; - -struct ConstructorCopy : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { -#ifndef VALIDATE - std::map M(Map); - benchmark::DoNotOptimize(M); -#else - std::map M(Map); - if (M != Map) - State.SkipWithError("Map copy not identical"); -#endif - } - } - - std::string name() const { return "BM_ConstructorCopy" + baseName(); } -}; - -struct ConstructorMove : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - std::map M(std::move(Map)); - benchmark::DoNotOptimize(M); - } - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_ConstructorMove" + baseName(); } -}; - -//*******************************************************************| -// Capacity | -//*******************************************************************| - -struct Empty : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1); - auto& Map = Data.Maps.front(); - for (auto _ : State) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.empty()); -#else - if (Map.empty()) - State.SkipWithError("Map contains an invalid number of elements."); -#endif - } - } - - std::string name() const { return "BM_Empty" + baseName(); } -}; - -struct Size : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1); - auto& Map = Data.Maps.front(); - for (auto _ : State) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.size()); -#else - if (Map.size() != MapSize) - State.SkipWithError("Map contains an invalid number of elements."); -#endif - } - } - - std::string name() const { return "BM_Size" + baseName(); } -}; - -//*******************************************************************| -// Modifiers | -//*******************************************************************| - -struct Clear : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - Map.clear(); - benchmark::DoNotOptimize(Map); - } - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_Clear" + baseName(); } -}; - -template -struct Insert : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.insert(std::make_pair(K, 1))); -#else - bool Inserted = Map.insert(std::make_pair(K, 1)).second; - if (Mode() == ::Mode::Hit) { - if (Inserted) - State.SkipWithError("Inserted a duplicate element"); - } else { - if (!Inserted) - State.SkipWithError("Failed to insert e new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_Insert" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct InsertHint : Base { - using Base::Base; - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto H = Data.Hints[I].begin(); - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.insert(*H, std::make_pair(K, 1))); -#else - auto Inserted = Map.insert(*H, std::make_pair(K, 1)); - if (Mode() == ::Mode::Hit) { - if (Inserted != *H) - State.SkipWithError("Inserted a duplicate element"); - } else { - if (++Inserted != *H) - State.SkipWithError("Failed to insert a new element"); - } -#endif - ++H; - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto Third = *(Data.Hints[I].begin() + 2); - for (auto K : Data.Keys) { - auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end(); -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.insert(Itor, std::make_pair(K, 1))); -#else - size_t Size = Map.size(); - Map.insert(Itor, std::make_pair(K, 1)); - if (Mode() == ::Mode::Hit) { - if (Size != Map.size()) - State.SkipWithError("Inserted a duplicate element"); - } else { - if (Size + 1 != Map.size()) - State.SkipWithError("Failed to insert a new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - void run(benchmark::State& State) const { - static constexpr auto h = Hint(); - run(State); - } - - std::string name() const { return "BM_InsertHint" + baseName() + Mode::name() + Hint::name(); } -}; - -template -struct InsertAssign : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.insert_or_assign(K, 1)); -#else - bool Inserted = Map.insert_or_assign(K, 1).second; - if (Mode() == ::Mode::Hit) { - if (Inserted) - State.SkipWithError("Inserted a duplicate element"); - } else { - if (!Inserted) - State.SkipWithError("Failed to insert e new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_InsertAssign" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct InsertAssignHint : Base { - using Base::Base; - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto H = Data.Hints[I].begin(); - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.insert_or_assign(*H, K, 1)); -#else - auto Inserted = Map.insert_or_assign(*H, K, 1); - if (Mode() == ::Mode::Hit) { - if (Inserted != *H) - State.SkipWithError("Inserted a duplicate element"); - } else { - if (++Inserted != *H) - State.SkipWithError("Failed to insert a new element"); - } -#endif - ++H; - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto Third = *(Data.Hints[I].begin() + 2); - for (auto K : Data.Keys) { - auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end(); -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.insert_or_assign(Itor, K, 1)); -#else - size_t Size = Map.size(); - Map.insert_or_assign(Itor, K, 1); - if (Mode() == ::Mode::Hit) { - if (Size != Map.size()) - State.SkipWithError("Inserted a duplicate element"); - } else { - if (Size + 1 != Map.size()) - State.SkipWithError("Failed to insert a new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - void run(benchmark::State& State) const { - static constexpr auto h = Hint(); - run(State); - } - - std::string name() const { return "BM_InsertAssignHint" + baseName() + Mode::name() + Hint::name(); } -}; - -template -struct Emplace : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.emplace(K, 1)); -#else - bool Inserted = Map.emplace(K, 1).second; - if (Mode() == ::Mode::Hit) { - if (Inserted) - State.SkipWithError("Emplaced a duplicate element"); - } else { - if (!Inserted) - State.SkipWithError("Failed to emplace a new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_Emplace" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct EmplaceHint : Base { - using Base::Base; - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto H = Data.Hints[I].begin(); - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.emplace_hint(*H, K, 1)); -#else - auto Inserted = Map.emplace_hint(*H, K, 1); - if (Mode() == ::Mode::Hit) { - if (Inserted != *H) - State.SkipWithError("Emplaced a duplicate element"); - } else { - if (++Inserted != *H) - State.SkipWithError("Failed to emplace a new element"); - } -#endif - ++H; - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto Third = *(Data.Hints[I].begin() + 2); - for (auto K : Data.Keys) { - auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end(); -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.emplace_hint(Itor, K, 1)); -#else - size_t Size = Map.size(); - Map.emplace_hint(Itor, K, 1); - if (Mode() == ::Mode::Hit) { - if (Size != Map.size()) - State.SkipWithError("Emplaced a duplicate element"); - } else { - if (Size + 1 != Map.size()) - State.SkipWithError("Failed to emplace a new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - void run(benchmark::State& State) const { - static constexpr auto h = Hint(); - run(State); - } - - std::string name() const { return "BM_EmplaceHint" + baseName() + Mode::name() + Hint::name(); } -}; - -template -struct TryEmplace : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.try_emplace(K, 1)); -#else - bool Inserted = Map.try_emplace(K, 1).second; - if (Mode() == ::Mode::Hit) { - if (Inserted) - State.SkipWithError("Emplaced a duplicate element"); - } else { - if (!Inserted) - State.SkipWithError("Failed to emplace a new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_TryEmplace" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct TryEmplaceHint : Base { - using Base::Base; - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto H = Data.Hints[I].begin(); - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.try_emplace(*H, K, 1)); -#else - auto Inserted = Map.try_emplace(*H, K, 1); - if (Mode() == ::Mode::Hit) { - if (Inserted != *H) - State.SkipWithError("Emplaced a duplicate element"); - } else { - if (++Inserted != *H) - State.SkipWithError("Failed to emplace a new element"); - } -#endif - ++H; - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - template < ::Hint hint> - typename std::enable_if::type run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - auto Third = *(Data.Hints[I].begin() + 2); - for (auto K : Data.Keys) { - auto Itor = hint == ::Hint::Begin ? Map.begin() : hint == ::Hint::Third ? Third : Map.end(); -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.try_emplace(Itor, K, 1)); -#else - size_t Size = Map.size(); - Map.try_emplace(Itor, K, 1); - if (Mode() == ::Mode::Hit) { - if (Size != Map.size()) - State.SkipWithError("Emplaced a duplicate element"); - } else { - if (Size + 1 != Map.size()) - State.SkipWithError("Failed to emplace a new element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - void run(benchmark::State& State) const { - static constexpr auto h = Hint(); - run(State); - } - - std::string name() const { return "BM_TryEmplaceHint" + baseName() + Mode::name() + Hint::name(); } -}; - -template -struct Erase : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.erase(K)); -#else - size_t I = Map.erase(K); - if (Mode() == ::Mode::Hit) { - if (I == 0) - State.SkipWithError("Did not find the existing element"); - } else { - if (I == 1) - State.SkipWithError("Did find the non-existing element"); - } -#endif - } - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_Erase" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct EraseIterator : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = - makeTestingSets(MapSize, Mode::Hit, Order::value == ::Order::Random ? Shuffle::Hints : Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (size_t I = 0; I < Data.Maps.size(); ++I) { - auto& Map = Data.Maps[I]; - for (auto H : Data.Hints[I]) { - benchmark::DoNotOptimize(Map.erase(H)); - } -#ifdef VALIDATE - if (!Map.empty()) - State.SkipWithError("Did not erase the entire map"); -#endif - } - - State.PauseTiming(); - Data = - makeTestingSets(MapSize, Mode::Hit, Order::value == ::Order::Random ? Shuffle::Hints : Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_EraseIterator" + baseName() + Order::name(); } -}; - -struct EraseRange : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000); - while (State.KeepRunningBatch(MapSize * Data.Maps.size())) { - for (auto& Map : Data.Maps) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.erase(Map.begin(), Map.end())); -#else - Map.erase(Map.begin(), Map.end()); - if (!Map.empty()) - State.SkipWithError("Did not erase the entire map"); -#endif - } - - State.PauseTiming(); - Data = makeTestingSets(MapSize, Mode::Hit, Shuffle::None, 1000); - State.ResumeTiming(); - } - } - - std::string name() const { return "BM_EraseRange" + baseName(); } -}; - -//*******************************************************************| -// Lookup | -//*******************************************************************| - -template -struct Count : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.count(K)); -#else - size_t I = Map.count(K); - if (Mode() == ::Mode::Hit) { - if (I == 0) - State.SkipWithError("Did not find the existing element"); - } else { - if (I == 1) - State.SkipWithError("Did find the non-existing element"); - } -#endif - } - } - } - - std::string name() const { return "BM_Count" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct Find : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.find(K)); -#else - auto Itor = Map.find(K); - if (Mode() == ::Mode::Hit) { - if (Itor == Map.end()) - State.SkipWithError("Did not find the existing element"); - } else { - if (Itor != Map.end()) - State.SkipWithError("Did find the non-existing element"); - } -#endif - } - } - } - - std::string name() const { return "BM_Find" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct EqualRange : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.equal_range(K)); -#else - auto Range = Map.equal_range(K); - if (Mode() == ::Mode::Hit) { - // Adjust validation for the last element. - auto Key = K; - if (Range.second == Map.end() && K == 2 * MapSize) { - --Range.second; - Key -= 2; - } - if (Range.first == Map.end() || Range.first->first != K || Range.second == Map.end() || - Range.second->first - 2 != Key) - State.SkipWithError("Did not find the existing element"); - } else { - if (Range.first == Map.end() || Range.first->first - 1 != K || Range.second == Map.end() || - Range.second->first - 1 != K) - State.SkipWithError("Did find the non-existing element"); - } -#endif - } - } - } - - std::string name() const { return "BM_EqualRange" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct LowerBound : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.lower_bound(K)); -#else - auto Itor = Map.lower_bound(K); - if (Mode() == ::Mode::Hit) { - if (Itor == Map.end() || Itor->first != K) - State.SkipWithError("Did not find the existing element"); - } else { - if (Itor == Map.end() || Itor->first - 1 != K) - State.SkipWithError("Did find the non-existing element"); - } -#endif - } - } - } - - std::string name() const { return "BM_LowerBound" + baseName() + Mode::name() + Order::name(); } -}; - -template -struct UpperBound : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(MapSize, Mode(), Order::value == ::Order::Random ? Shuffle::Keys : Shuffle::None, 1); - auto& Map = Data.Maps.front(); - while (State.KeepRunningBatch(MapSize)) { - for (auto K : Data.Keys) { -#ifndef VALIDATE - benchmark::DoNotOptimize(Map.upper_bound(K)); -#else - std::map::iterator Itor = Map.upper_bound(K); - if (Mode() == ::Mode::Hit) { - // Adjust validation for the last element. - auto Key = K; - if (Itor == Map.end() && K == 2 * MapSize) { - --Itor; - Key -= 2; - } - if (Itor == Map.end() || Itor->first - 2 != Key) - State.SkipWithError("Did not find the existing element"); - } else { - if (Itor == Map.end() || Itor->first - 1 != K) - State.SkipWithError("Did find the non-existing element"); - } -#endif - } - } - } - - std::string name() const { return "BM_UpperBound" + baseName() + Mode::name() + Order::name(); } -}; - -} // namespace int main(int argc, char** argv) { - benchmark::Initialize(&argc, argv); - if (benchmark::ReportUnrecognizedArguments(argc, argv)) - return 1; - -#ifdef VALIDATE - const std::vector MapSize{10}; -#else - const std::vector MapSize{10, 100, 1000, 10000, 100000, 1000000}; -#endif - - // Member functions - makeCartesianProductBenchmark(); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - - // Capacity - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - - // Modifiers - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - - // Lookup - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); - makeCartesianProductBenchmark(MapSize); + support::associative_container_benchmarks>("std::map"); + support::associative_container_benchmarks>("std::map"); + benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; } diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/set.bench.cpp new file mode 100644 index 0000000000000..6a8de0862f2ae --- /dev/null +++ b/libcxx/test/benchmarks/containers/set.bench.cpp @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// 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 "associative_container_benchmarks.h" +#include "benchmark/benchmark.h" + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::set"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} From c0cf8868578eb32d56ee843505815485e7e33309 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 14:31:16 -0500 Subject: [PATCH 02/16] Alignment of buffer --- .../benchmarks/containers/associative_container_benchmarks.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h index 3bddc0c73b54e..18dcc3c110770 100644 --- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h @@ -81,7 +81,7 @@ void associative_container_benchmarks(std::string container) { // PauseTiming() and ResumeTiming(). static constexpr std::size_t BatchSize = 10; - struct ScratchSpace { + struct alignas(Container) ScratchSpace { char storage[sizeof(Container)]; }; From b09ebaba3e5b2cc035ddec38d293d69da322ebb8 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 14:31:26 -0500 Subject: [PATCH 03/16] Use std-at-least-c++26 --- libcxx/test/benchmarks/containers/flat_map.bench.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp index 39971c9319e6c..ada198892ff96 100644 --- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// -// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20, c++23 +// REQUIRES: std-at-least-c++26 #include #include From 00927f02509fcfea763eab4fba69169033c02f48 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 14:35:20 -0500 Subject: [PATCH 04/16] Replace previous set benchmarks --- .../containers/ordered_set.bench.cpp | 232 ------------------ 1 file changed, 232 deletions(-) delete mode 100644 libcxx/test/benchmarks/containers/ordered_set.bench.cpp diff --git a/libcxx/test/benchmarks/containers/ordered_set.bench.cpp b/libcxx/test/benchmarks/containers/ordered_set.bench.cpp deleted file mode 100644 index cb68902c6dcc8..0000000000000 --- a/libcxx/test/benchmarks/containers/ordered_set.bench.cpp +++ /dev/null @@ -1,232 +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 - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "../CartesianBenchmarks.h" -#include "benchmark/benchmark.h" -#include "test_macros.h" - -namespace { - -enum class HitType { Hit, Miss }; - -struct AllHitTypes : EnumValuesAsTuple { - static constexpr const char* Names[] = {"Hit", "Miss"}; -}; - -enum class AccessPattern { Ordered, Random }; - -struct AllAccessPattern : EnumValuesAsTuple { - static constexpr const char* Names[] = {"Ordered", "Random"}; -}; - -void sortKeysBy(std::vector& Keys, AccessPattern AP) { - if (AP == AccessPattern::Random) { - std::random_device R; - std::mt19937 M(R()); - std::shuffle(std::begin(Keys), std::end(Keys), M); - } -} - -struct TestSets { - std::vector > Sets; - std::vector Keys; -}; - -TestSets makeTestingSets(size_t TableSize, size_t NumTables, HitType Hit, AccessPattern Access) { - TestSets R; - R.Sets.resize(1); - - for (uint64_t I = 0; I < TableSize; ++I) { - R.Sets[0].insert(2 * I); - R.Keys.push_back(Hit == HitType::Hit ? 2 * I : 2 * I + 1); - } - R.Sets.resize(NumTables, R.Sets[0]); - sortKeysBy(R.Keys, Access); - - return R; -} - -struct Base { - size_t TableSize; - size_t NumTables; - Base(size_t T, size_t N) : TableSize(T), NumTables(N) {} - - bool skip() const { - size_t Total = TableSize * NumTables; - return Total < 100 || Total > 1000000; - } - - std::string baseName() const { - return "_TableSize" + std::to_string(TableSize) + "_NumTables" + std::to_string(NumTables); - } -}; - -template -struct Create : Base { - using Base::Base; - - void run(benchmark::State& State) const { - std::vector Keys(TableSize); - std::iota(Keys.begin(), Keys.end(), uint64_t{0}); - sortKeysBy(Keys, Access()); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - std::vector> Sets(NumTables); - for (auto K : Keys) { - for (auto& Set : Sets) { - benchmark::DoNotOptimize(Set.insert(K)); - } - } - } - } - - std::string name() const { return "BM_Create" + Access::name() + baseName(); } -}; - -template -struct Find : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(TableSize, NumTables, Hit(), Access()); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - for (auto K : Data.Keys) { - for (auto& Set : Data.Sets) { - benchmark::DoNotOptimize(Set.find(K)); - } - } - } - } - - std::string name() const { return "BM_Find" + Hit::name() + Access::name() + baseName(); } -}; - -template -struct FindNeEnd : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(TableSize, NumTables, Hit(), Access()); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - for (auto K : Data.Keys) { - for (auto& Set : Data.Sets) { - benchmark::DoNotOptimize(Set.find(K) != Set.end()); - } - } - } - } - - std::string name() const { return "BM_FindNeEnd" + Hit::name() + Access::name() + baseName(); } -}; - -template -struct InsertHit : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(TableSize, NumTables, HitType::Hit, Access()); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - for (auto K : Data.Keys) { - for (auto& Set : Data.Sets) { - benchmark::DoNotOptimize(Set.insert(K)); - } - } - } - } - - std::string name() const { return "BM_InsertHit" + Access::name() + baseName(); } -}; - -template -struct InsertMissAndErase : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(TableSize, NumTables, HitType::Miss, Access()); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - for (auto K : Data.Keys) { - for (auto& Set : Data.Sets) { - benchmark::DoNotOptimize(Set.erase(Set.insert(K).first)); - } - } - } - } - - std::string name() const { return "BM_InsertMissAndErase" + Access::name() + baseName(); } -}; - -struct IterateRangeFor : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(TableSize, NumTables, HitType::Miss, AccessPattern::Ordered); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - for (auto& Set : Data.Sets) { - for (auto& V : Set) { - benchmark::DoNotOptimize(const_cast::reference>(V)); - } - } - } - } - - std::string name() const { return "BM_IterateRangeFor" + baseName(); } -}; - -struct IterateBeginEnd : Base { - using Base::Base; - - void run(benchmark::State& State) const { - auto Data = makeTestingSets(TableSize, NumTables, HitType::Miss, AccessPattern::Ordered); - - while (State.KeepRunningBatch(TableSize * NumTables)) { - for (auto& Set : Data.Sets) { - for (auto it = Set.begin(); it != Set.end(); ++it) { - benchmark::DoNotOptimize(const_cast::reference>(*it)); - } - } - } - } - - std::string name() const { return "BM_IterateBeginEnd" + baseName(); } -}; - -} // namespace - -int main(int argc, char** argv) { - benchmark::Initialize(&argc, argv); - if (benchmark::ReportUnrecognizedArguments(argc, argv)) - return 1; - - const std::vector TableSize{1, 10, 100, 1000, 10000, 100000, 1000000}; - const std::vector NumTables{1, 10, 100, 1000, 10000, 100000, 1000000}; - - makeCartesianProductBenchmark(TableSize, NumTables); - makeCartesianProductBenchmark(TableSize, NumTables); - makeCartesianProductBenchmark(TableSize, NumTables); - makeCartesianProductBenchmark(TableSize, NumTables); - makeCartesianProductBenchmark(TableSize, NumTables); - makeCartesianProductBenchmark(TableSize, NumTables); - makeCartesianProductBenchmark(TableSize, NumTables); - benchmark::RunSpecifiedBenchmarks(); -} From 68fa577087908d2efc67b049102d44d367ff6232 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 14:41:58 -0500 Subject: [PATCH 05/16] Tackle unordered containers too --- .../associative_container_benchmarks.h | 230 ++++++++--------- .../benchmarks/containers/flat_map.bench.cpp | 9 + .../test/benchmarks/containers/map.bench.cpp | 9 + .../test/benchmarks/containers/set.bench.cpp | 8 + .../containers/unordered_map.bench.cpp | 34 +++ .../containers/unordered_set.bench.cpp | 242 +----------------- 6 files changed, 180 insertions(+), 352 deletions(-) create mode 100644 libcxx/test/benchmarks/containers/unordered_map.bench.cpp diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h index 18dcc3c110770..947186dc36742 100644 --- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h @@ -11,48 +11,23 @@ #include #include -#include -#include #include -#include #include #include #include "benchmark/benchmark.h" #include "../GenerateInput.h" -#include "test_macros.h" namespace support { template -struct adapt_operations; - -template -struct adapt_operations> { - using ValueType = typename std::set::value_type; - using KeyType = typename std::set::key_type; - static ValueType value_from_key(KeyType const& k) { return k; } - static KeyType key_from_value(ValueType const& value) { return value; } +struct adapt_operations { + // using ValueType = ...; + // using KeyType = ...; + // static ValueType value_from_key(KeyType const& k); + // static KeyType key_from_value(ValueType const& value); }; -template -struct adapt_operations> { - using ValueType = typename std::map::value_type; - using KeyType = typename std::map::key_type; - static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } - static KeyType key_from_value(ValueType const& value) { return value.first; } -}; - -#if TEST_STD_VER >= 26 -template -struct adapt_operations> { - using ValueType = typename std::map::value_type; - using KeyType = typename std::map::key_type; - static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } - static KeyType key_from_value(ValueType const& value) { return value.first; } -}; -#endif - template void associative_container_benchmarks(std::string container) { using Key = typename Container::key_type; @@ -67,7 +42,7 @@ void associative_container_benchmarks(std::string container) { return std::vector(keys.begin(), keys.end()); }; - auto add_dummy_mapped_type = [](std::vector const& keys) { + auto make_value_types = [](std::vector const& keys) { std::vector kv; for (Key const& k : keys) kv.push_back(adapt_operations::value_from_key(k)); @@ -90,7 +65,7 @@ void associative_container_benchmarks(std::string container) { ///////////////////////// benchmark::RegisterBenchmark(container + "::ctor(const&)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Container src(in.begin(), in.end()); ScratchSpace c[BatchSize]; @@ -114,7 +89,7 @@ void associative_container_benchmarks(std::string container) { std::mt19937 randomness; std::vector keys = generate_unique_keys(size); std::shuffle(keys.begin(), keys.end(), randomness); - std::vector in = add_dummy_mapped_type(keys); + std::vector in = make_value_types(keys); ScratchSpace c[BatchSize]; while (st.KeepRunningBatch(BatchSize)) { @@ -136,7 +111,7 @@ void associative_container_benchmarks(std::string container) { const std::size_t size = st.range(0); std::vector keys = generate_unique_keys(size); std::sort(keys.begin(), keys.end()); - std::vector in = add_dummy_mapped_type(keys); + std::vector in = make_value_types(keys); ScratchSpace c[BatchSize]; while (st.KeepRunningBatch(BatchSize)) { @@ -159,7 +134,7 @@ void associative_container_benchmarks(std::string container) { ///////////////////////// benchmark::RegisterBenchmark(container + "::operator=(const&)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Container src(in.begin(), in.end()); Container c[BatchSize]; @@ -183,7 +158,7 @@ void associative_container_benchmarks(std::string container) { ///////////////////////// benchmark::RegisterBenchmark(container + "::insert(value) (already present)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Value to_insert = in[in.size() / 2]; // pick any existing value std::vector c(BatchSize, Container(in.begin(), in.end())); @@ -201,7 +176,7 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::insert(value) (new value)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + std::vector in = make_value_types(generate_unique_keys(size + 1)); Value to_insert = in.back(); in.pop_back(); std::vector c(BatchSize, Container(in.begin(), in.end())); @@ -221,59 +196,63 @@ void associative_container_benchmarks(std::string container) { } })->Arg(1024); - benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) { - const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); - Value to_insert = in.back(); - in.pop_back(); - - std::vector c(BatchSize, Container(in.begin(), in.end())); - typename Container::iterator hints[BatchSize]; - for (std::size_t i = 0; i != BatchSize; ++i) { - hints[i] = c[i].lower_bound(get_key(to_insert)); - } + // The insert(hint, ...) methods are only relevant for ordered containers, and we lack + // a good way to compute a hint for unordered ones. + if constexpr (requires(Container c, Key k) { c.lower_bound(k); }) { + benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = make_value_types(generate_unique_keys(size + 1)); + Value to_insert = in.back(); + in.pop_back(); - while (st.KeepRunningBatch(BatchSize)) { + std::vector c(BatchSize, Container(in.begin(), in.end())); + typename Container::iterator hints[BatchSize]; for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].insert(hints[i], to_insert); - benchmark::DoNotOptimize(c[i]); - benchmark::ClobberMemory(); + hints[i] = c[i].lower_bound(get_key(to_insert)); } - st.PauseTiming(); - for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].erase(get_key(to_insert)); - hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(hints[i], to_insert); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } + + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(get_key(to_insert)); + hints[i] = c[i].lower_bound(get_key(to_insert)); // refresh hints in case of invalidation + } + st.ResumeTiming(); } - st.ResumeTiming(); - } - })->Arg(1024); + })->Arg(1024); - benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) { - const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); - Value to_insert = in.back(); - in.pop_back(); - std::vector c(BatchSize, Container(in.begin(), in.end())); + benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = make_value_types(generate_unique_keys(size + 1)); + Value to_insert = in.back(); + in.pop_back(); + std::vector c(BatchSize, Container(in.begin(), in.end())); - while (st.KeepRunningBatch(BatchSize)) { - for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].insert(c[i].begin(), to_insert); - benchmark::DoNotOptimize(c[i]); - benchmark::ClobberMemory(); - } + while (st.KeepRunningBatch(BatchSize)) { + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].insert(c[i].begin(), to_insert); + benchmark::DoNotOptimize(c[i]); + benchmark::ClobberMemory(); + } - st.PauseTiming(); - for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].erase(get_key(to_insert)); + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(get_key(to_insert)); + } + st.ResumeTiming(); } - st.ResumeTiming(); - } - })->Arg(1024); + })->Arg(1024); + } benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (all new keys)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size + (size / 10))); + std::vector in = make_value_types(generate_unique_keys(size + (size / 10))); // Populate a container with a small number of elements, that's what containers will start with. std::vector small; @@ -296,7 +275,7 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (half new keys)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); // Populate a container that already contains half the elements we'll try inserting, // that's what our container will start with. @@ -322,7 +301,7 @@ void associative_container_benchmarks(std::string container) { ///////////////////////// benchmark::RegisterBenchmark(container + "::erase(key) (existent)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Value element = in[in.size() / 2]; // pick any element std::vector c(BatchSize, Container(in.begin(), in.end())); @@ -343,7 +322,7 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::erase(key) (non-existent)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + std::vector in = make_value_types(generate_unique_keys(size + 1)); Value element = in.back(); in.pop_back(); Container c(in.begin(), in.end()); @@ -361,7 +340,7 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::erase(iterator)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Value element = in[in.size() / 2]; // pick any element std::vector c; @@ -388,7 +367,7 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::erase(iterator, iterator) (erase half the container)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Container c(in.begin(), in.end()); auto first = std::next(c.begin(), c.size() / 4); @@ -408,7 +387,7 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::clear()", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Container c(in.begin(), in.end()); for (auto _ : st) { @@ -428,7 +407,7 @@ void associative_container_benchmarks(std::string container) { auto bench_with_existent_key = [=](auto func) { return [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size)); + std::vector in = make_value_types(generate_unique_keys(size)); Value element = in[in.size() / 2]; // pick any element Container c(in.begin(), in.end()); @@ -446,7 +425,7 @@ void associative_container_benchmarks(std::string container) { auto bench_with_nonexistent_key = [=](auto func) { return [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = add_dummy_mapped_type(generate_unique_keys(size + 1)); + std::vector in = make_value_types(generate_unique_keys(size + 1)); Value element = in.back(); in.pop_back(); Container c(in.begin(), in.end()); @@ -491,44 +470,47 @@ void associative_container_benchmarks(std::string container) { })) ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::lower_bound(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { - return c.lower_bound(get_key(element)); - })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::lower_bound(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.lower_bound(get_key(element)); - })) - ->Arg(1024); - - benchmark::RegisterBenchmark( - container + "::upper_bound(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { - return c.upper_bound(get_key(element)); - })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::upper_bound(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.upper_bound(get_key(element)); - })) - ->Arg(1024); - - benchmark::RegisterBenchmark( - container + "::equal_range(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { - return c.equal_range(get_key(element)); - })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::equal_range(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.equal_range(get_key(element)); - })) - ->Arg(1024); + // Only for ordered containers + if constexpr (requires(Container c, Key key) { c.lower_bound(key); }) { + benchmark::RegisterBenchmark( + container + "::lower_bound(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { + return c.lower_bound(get_key(element)); + })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::lower_bound(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.lower_bound(get_key(element)); + })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::upper_bound(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { + return c.upper_bound(get_key(element)); + })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::upper_bound(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.upper_bound(get_key(element)); + })) + ->Arg(1024); + + benchmark::RegisterBenchmark( + container + "::equal_range(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { + return c.equal_range(get_key(element)); + })) + ->Arg(1024); + benchmark::RegisterBenchmark( + container + "::equal_range(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.equal_range(get_key(element)); + })) + ->Arg(1024); + } } } // namespace support diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp index ada198892ff96..1c50e52cf8043 100644 --- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp @@ -12,8 +12,17 @@ #include #include "associative_container_benchmarks.h" +#include "../GenerateInput.h" #include "benchmark/benchmark.h" +template +struct support::adapt_operations> { + using ValueType = typename std::map::value_type; + using KeyType = typename std::map::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } +}; + int main(int argc, char** argv) { support::associative_container_benchmarks>("std::flat_map"); support::associative_container_benchmarks>("std::flat_map"); diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/map.bench.cpp index 5c5ba7cc9d3db..73c9e806ce1f0 100644 --- a/libcxx/test/benchmarks/containers/map.bench.cpp +++ b/libcxx/test/benchmarks/containers/map.bench.cpp @@ -12,8 +12,17 @@ #include #include "associative_container_benchmarks.h" +#include "../GenerateInput.h" #include "benchmark/benchmark.h" +template +struct support::adapt_operations> { + using ValueType = typename std::map::value_type; + using KeyType = typename std::map::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } +}; + int main(int argc, char** argv) { support::associative_container_benchmarks>("std::map"); support::associative_container_benchmarks>("std::map"); diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/set.bench.cpp index 6a8de0862f2ae..42f0777f967b2 100644 --- a/libcxx/test/benchmarks/containers/set.bench.cpp +++ b/libcxx/test/benchmarks/containers/set.bench.cpp @@ -13,6 +13,14 @@ #include "associative_container_benchmarks.h" #include "benchmark/benchmark.h" +template +struct support::adapt_operations> { + using ValueType = typename std::set::value_type; + using KeyType = typename std::set::key_type; + static ValueType value_from_key(KeyType const& k) { return k; } + static KeyType key_from_value(ValueType const& value) { return value; } +}; + int main(int argc, char** argv) { support::associative_container_benchmarks>("std::set"); diff --git a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp new file mode 100644 index 0000000000000..51c6cf1e3cf11 --- /dev/null +++ b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 "associative_container_benchmarks.h" +#include "../GenerateInput.h" +#include "benchmark/benchmark.h" + +template +struct support::adapt_operations> { + using ValueType = typename std::unordered_map::value_type; + using KeyType = typename std::unordered_map::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } +}; + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::unordered_map"); + support::associative_container_benchmarks>("std::unordered_map"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp index ad8d0feaa0436..f1661aaeb3948 100644 --- a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp +++ b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp @@ -8,238 +8,24 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 -#include -#include -#include -#include #include -#include +#include "associative_container_benchmarks.h" #include "benchmark/benchmark.h" -#include "container_benchmarks.h" -#include "../GenerateInput.h" -#include "test_macros.h" - -using namespace ContainerBenchmarks; - -constexpr std::size_t TestNumInputs = 1024; - -// The purpose of this hash function is to NOT be implemented as the identity function, -// which is how std::hash is implemented for smaller integral types. -struct NonIdentityScalarHash : std::hash {}; - -// The sole purpose of this comparator is to be used in BM_Rehash, where -// we need something slow enough to be easily noticable in benchmark results. -// The default implementation of operator== for strings seems to be a little -// too fast for that specific benchmark to reliably show a noticeable -// improvement, but unoptimized bytewise comparison fits just right. -// Early return is there just for convenience, since we only compare strings -// of equal length in BM_Rehash. -struct SlowStringEq { - SlowStringEq() = default; - inline TEST_ALWAYS_INLINE bool operator()(const std::string& lhs, const std::string& rhs) const { - if (lhs.size() != rhs.size()) - return false; - - bool eq = true; - for (size_t i = 0; i < lhs.size(); ++i) { - eq &= lhs[i] == rhs[i]; - } - return eq; - } +template +struct support::adapt_operations> { + using ValueType = typename std::unordered_set::value_type; + using KeyType = typename std::unordered_set::key_type; + static ValueType value_from_key(KeyType const& k) { return k; } + static KeyType key_from_value(ValueType const& value) { return value; } }; -//----------------------------------------------------------------------------// -// BM_InsertValue -// ---------------------------------------------------------------------------// - -// Sorted Ascending // -BENCHMARK_CAPTURE( - BM_InsertValue, unordered_set_uint32, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_InsertValue, unordered_set_uint32_sorted, std::unordered_set{}, getSortedIntegerInputs) - ->Arg(TestNumInputs); - -// Top Bytes // -BENCHMARK_CAPTURE(BM_InsertValue, - unordered_set_top_bits_uint32, - std::unordered_set{}, - getSortedTopBitsIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_InsertValueRehash, - unordered_set_top_bits_uint32, - std::unordered_set{}, - getSortedTopBitsIntegerInputs) - ->Arg(TestNumInputs); - -// String // -BENCHMARK_CAPTURE(BM_InsertValue, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_InsertValueRehash, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -// Prefixed String // -BENCHMARK_CAPTURE( - BM_InsertValue, unordered_set_prefixed_string, std::unordered_set{}, getPrefixedRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_InsertValueRehash, - unordered_set_prefixed_string, - std::unordered_set{}, - getPrefixedRandomStringInputs) - ->Arg(TestNumInputs); - -//----------------------------------------------------------------------------// -// BM_Find -// ---------------------------------------------------------------------------// - -// Random // -BENCHMARK_CAPTURE( - BM_Find, unordered_set_random_uint64, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, - unordered_set_random_uint64, - std::unordered_set{}, - getRandomIntegerInputs) - ->Arg(TestNumInputs); - -// Sorted // -BENCHMARK_CAPTURE( - BM_Find, unordered_set_sorted_uint64, std::unordered_set{}, getSortedIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, - unordered_set_sorted_uint64, - std::unordered_set{}, - getSortedIntegerInputs) - ->Arg(TestNumInputs); - -// Sorted // -#ifndef TEST_HAS_NO_INT128 -BENCHMARK_CAPTURE(BM_Find, - unordered_set_sorted_uint128, - std::unordered_set<__uint128_t>{}, - getSortedTopBitsIntegerInputs<__uint128_t>) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, - unordered_set_sorted_uint128, - std::unordered_set<__uint128_t>{}, - getSortedTopBitsIntegerInputs<__uint128_t>) - ->Arg(TestNumInputs); -#endif - -// Sorted // -BENCHMARK_CAPTURE( - BM_Find, unordered_set_sorted_uint32, std::unordered_set{}, getSortedIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, - unordered_set_sorted_uint32, - std::unordered_set{}, - getSortedIntegerInputs) - ->Arg(TestNumInputs); - -// Sorted Ascending // -BENCHMARK_CAPTURE( - BM_Find, unordered_set_sorted_large_uint64, std::unordered_set{}, getSortedLargeIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, - unordered_set_sorted_large_uint64, - std::unordered_set{}, - getSortedLargeIntegerInputs) - ->Arg(TestNumInputs); - -// Top Bits // -BENCHMARK_CAPTURE( - BM_Find, unordered_set_top_bits_uint64, std::unordered_set{}, getSortedTopBitsIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, - unordered_set_top_bits_uint64, - std::unordered_set{}, - getSortedTopBitsIntegerInputs) - ->Arg(TestNumInputs); - -// String // -BENCHMARK_CAPTURE(BM_Find, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_FindRehash, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -// Prefixed String // -BENCHMARK_CAPTURE( - BM_Find, unordered_set_prefixed_string, std::unordered_set{}, getPrefixedRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_FindRehash, unordered_set_prefixed_string, std::unordered_set{}, getPrefixedRandomStringInputs) - ->Arg(TestNumInputs); - -//----------------------------------------------------------------------------// -// BM_Rehash -// ---------------------------------------------------------------------------// - -BENCHMARK_CAPTURE(BM_Rehash, - unordered_set_string_arg, - std::unordered_set, SlowStringEq>{}, - getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_Rehash, unordered_set_int_arg, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -//----------------------------------------------------------------------------// -// BM_Compare -// ---------------------------------------------------------------------------// - -BENCHMARK_CAPTURE( - BM_Compare_same_container, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_Compare_same_container, unordered_set_int, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_Compare_different_containers, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_Compare_different_containers, unordered_set_int, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -/////////////////////////////////////////////////////////////////////////////// -BENCHMARK_CAPTURE(BM_InsertDuplicate, unordered_set_int, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); -BENCHMARK_CAPTURE(BM_InsertDuplicate, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE(BM_EmplaceDuplicate, unordered_set_int, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); -BENCHMARK_CAPTURE(BM_EmplaceDuplicate, unordered_set_string, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_InsertDuplicate, unordered_set_int_insert_arg, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); -BENCHMARK_CAPTURE( - BM_InsertDuplicate, unordered_set_string_insert_arg, std::unordered_set{}, getRandomStringInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_EmplaceDuplicate, unordered_set_int_insert_arg, std::unordered_set{}, getRandomIntegerInputs) - ->Arg(TestNumInputs); - -BENCHMARK_CAPTURE( - BM_EmplaceDuplicate, unordered_set_string_arg, std::unordered_set{}, getRandomCStringInputs) - ->Arg(TestNumInputs); +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::unordered_set"); -BENCHMARK_MAIN(); + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} From c7788163548d42bbb92e6625776622a77c2df9dd Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 15:15:48 -0500 Subject: [PATCH 06/16] Handle multi-key containers too --- .../associative_container_benchmarks.h | 34 +++++++++++++++-- .../benchmarks/containers/flat_map.bench.cpp | 4 ++ .../test/benchmarks/containers/map.bench.cpp | 4 ++ .../benchmarks/containers/multimap.bench.cpp | 37 ++++++++++++++++++ .../benchmarks/containers/multiset.bench.cpp | 34 +++++++++++++++++ .../test/benchmarks/containers/set.bench.cpp | 4 ++ .../containers/unordered_map.bench.cpp | 7 +++- .../containers/unordered_multimap.bench.cpp | 38 +++++++++++++++++++ .../containers/unordered_multiset.bench.cpp | 34 +++++++++++++++++ .../containers/unordered_set.bench.cpp | 4 ++ 10 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 libcxx/test/benchmarks/containers/multimap.bench.cpp create mode 100644 libcxx/test/benchmarks/containers/multiset.bench.cpp create mode 100644 libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp create mode 100644 libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h index 947186dc36742..252b211e0456b 100644 --- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative_container_benchmarks.h @@ -26,6 +26,9 @@ struct adapt_operations { // using KeyType = ...; // static ValueType value_from_key(KeyType const& k); // static KeyType key_from_value(ValueType const& value); + + // using InsertionResult = ...; + // static Container::iterator get_iterator(InsertionResult const&); }; template @@ -169,8 +172,17 @@ void associative_container_benchmarks(std::string container) { benchmark::ClobberMemory(); } - // There is no cleanup to do, since associative containers don't insert - // if the key is already present. + st.PauseTiming(); + // Unique-key containers will not insert above, but multi-key containers will insert + // the key a second time. Restore the invariant that there's a single copy of each + // key in the container. + for (std::size_t i = 0; i != BatchSize; ++i) { + auto const& key = get_key(to_insert); + if (c[i].count(key) > 1) { + c[i].erase(key); + } + } + st.ResumeTiming(); } })->Arg(1024); @@ -359,7 +371,7 @@ void associative_container_benchmarks(std::string container) { st.PauseTiming(); for (std::size_t i = 0; i != BatchSize; ++i) { - iterators[i] = c[i].insert(element).first; + iterators[i] = adapt_operations::get_iterator(c[i].insert(element)); } st.ResumeTiming(); } @@ -401,6 +413,22 @@ void associative_container_benchmarks(std::string container) { } })->Arg(1024); + ///////////////////////// + // Capacity + ///////////////////////// + benchmark::RegisterBenchmark(container + "::size()", [=](auto& st) { + const std::size_t size = st.range(0); + std::vector in = make_value_types(generate_unique_keys(size)); + Container c(in.begin(), in.end()); + + for (auto _ : st) { + auto res = c.size(); + benchmark::DoNotOptimize(c); + benchmark::DoNotOptimize(res); + benchmark::ClobberMemory(); + } + })->Arg(1024); + ///////////////////////// // Query ///////////////////////// diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/flat_map.bench.cpp index 1c50e52cf8043..013d1c4dcc464 100644 --- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/flat_map.bench.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "associative_container_benchmarks.h" #include "../GenerateInput.h" @@ -21,6 +22,9 @@ struct support::adapt_operations> { using KeyType = typename std::map::key_type; static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } static KeyType key_from_value(ValueType const& value) { return value.first; } + + using InsertionResult = std::pair::iterator, bool>; + static auto get_iterator(InsertionResult const& result) { return result.first; } }; int main(int argc, char** argv) { diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/map.bench.cpp index 73c9e806ce1f0..21d6af6af40c5 100644 --- a/libcxx/test/benchmarks/containers/map.bench.cpp +++ b/libcxx/test/benchmarks/containers/map.bench.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "associative_container_benchmarks.h" #include "../GenerateInput.h" @@ -21,6 +22,9 @@ struct support::adapt_operations> { using KeyType = typename std::map::key_type; static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } static KeyType key_from_value(ValueType const& value) { return value.first; } + + using InsertionResult = std::pair::iterator, bool>; + static auto get_iterator(InsertionResult const& result) { return result.first; } }; int main(int argc, char** argv) { diff --git a/libcxx/test/benchmarks/containers/multimap.bench.cpp b/libcxx/test/benchmarks/containers/multimap.bench.cpp new file mode 100644 index 0000000000000..1a983090d3826 --- /dev/null +++ b/libcxx/test/benchmarks/containers/multimap.bench.cpp @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// 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 "associative_container_benchmarks.h" +#include "../GenerateInput.h" +#include "benchmark/benchmark.h" + +template +struct support::adapt_operations> { + using ValueType = typename std::multimap::value_type; + using KeyType = typename std::multimap::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } + + using InsertionResult = typename std::multimap::iterator; + static auto get_iterator(InsertionResult const& result) { return result; } +}; + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::multimap"); + support::associative_container_benchmarks>("std::multimap"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/libcxx/test/benchmarks/containers/multiset.bench.cpp b/libcxx/test/benchmarks/containers/multiset.bench.cpp new file mode 100644 index 0000000000000..894f159a52e4a --- /dev/null +++ b/libcxx/test/benchmarks/containers/multiset.bench.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 "associative_container_benchmarks.h" +#include "benchmark/benchmark.h" + +template +struct support::adapt_operations> { + using ValueType = typename std::multiset::value_type; + using KeyType = typename std::multiset::key_type; + static ValueType value_from_key(KeyType const& k) { return k; } + static KeyType key_from_value(ValueType const& value) { return value; } + + using InsertionResult = typename std::multiset::iterator; + static auto get_iterator(InsertionResult const& result) { return result; } +}; + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::multiset"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/set.bench.cpp index 42f0777f967b2..6b7b142c792ba 100644 --- a/libcxx/test/benchmarks/containers/set.bench.cpp +++ b/libcxx/test/benchmarks/containers/set.bench.cpp @@ -9,6 +9,7 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 #include +#include #include "associative_container_benchmarks.h" #include "benchmark/benchmark.h" @@ -19,6 +20,9 @@ struct support::adapt_operations> { using KeyType = typename std::set::key_type; static ValueType value_from_key(KeyType const& k) { return k; } static KeyType key_from_value(ValueType const& value) { return value; } + + using InsertionResult = std::pair::iterator, bool>; + static auto get_iterator(InsertionResult const& result) { return result.first; } }; int main(int argc, char** argv) { diff --git a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp index 51c6cf1e3cf11..707cc3e584462 100644 --- a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/unordered_map.bench.cpp @@ -10,6 +10,7 @@ #include #include +#include #include "associative_container_benchmarks.h" #include "../GenerateInput.h" @@ -21,11 +22,15 @@ struct support::adapt_operations> { using KeyType = typename std::unordered_map::key_type; static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } static KeyType key_from_value(ValueType const& value) { return value.first; } + + using InsertionResult = std::pair::iterator, bool>; + static auto get_iterator(InsertionResult const& result) { return result.first; } }; int main(int argc, char** argv) { support::associative_container_benchmarks>("std::unordered_map"); - support::associative_container_benchmarks>("std::unordered_map"); + support::associative_container_benchmarks>( + "std::unordered_map"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); diff --git a/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp b/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp new file mode 100644 index 0000000000000..ae7caf0f453c7 --- /dev/null +++ b/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp @@ -0,0 +1,38 @@ +//===----------------------------------------------------------------------===// +// +// 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 "associative_container_benchmarks.h" +#include "../GenerateInput.h" +#include "benchmark/benchmark.h" + +template +struct support::adapt_operations> { + using ValueType = typename std::unordered_multimap::value_type; + using KeyType = typename std::unordered_multimap::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } + + using InsertionResult = typename std::unordered_multimap::iterator; + static auto get_iterator(InsertionResult const& result) { return result; } +}; + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::unordered_multimap"); + support::associative_container_benchmarks>( + "std::unordered_multimap"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp b/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp new file mode 100644 index 0000000000000..4888b01bfeba0 --- /dev/null +++ b/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp @@ -0,0 +1,34 @@ +//===----------------------------------------------------------------------===// +// +// 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 "associative_container_benchmarks.h" +#include "benchmark/benchmark.h" + +template +struct support::adapt_operations> { + using ValueType = typename std::unordered_multiset::value_type; + using KeyType = typename std::unordered_multiset::key_type; + static ValueType value_from_key(KeyType const& k) { return k; } + static KeyType key_from_value(ValueType const& value) { return value; } + + using InsertionResult = typename std::unordered_multiset::iterator; + static auto get_iterator(InsertionResult const& result) { return result; } +}; + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::unordered_multiset"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} diff --git a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp index f1661aaeb3948..56420bdaadfbf 100644 --- a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp +++ b/libcxx/test/benchmarks/containers/unordered_set.bench.cpp @@ -9,6 +9,7 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 #include +#include #include "associative_container_benchmarks.h" #include "benchmark/benchmark.h" @@ -19,6 +20,9 @@ struct support::adapt_operations> { using KeyType = typename std::unordered_set::key_type; static ValueType value_from_key(KeyType const& k) { return k; } static KeyType key_from_value(ValueType const& value) { return value; } + + using InsertionResult = std::pair::iterator, bool>; + static auto get_iterator(InsertionResult const& result) { return result.first; } }; int main(int argc, char** argv) { From d732fe382cc17747137a1d2e7b9a75b8458ad36f Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 15:18:46 -0500 Subject: [PATCH 07/16] Move associative containers to subdirectory --- .../{ => associative}/associative_container_benchmarks.h | 2 +- .../benchmarks/containers/{ => associative}/flat_map.bench.cpp | 2 +- .../test/benchmarks/containers/{ => associative}/map.bench.cpp | 2 +- .../benchmarks/containers/{ => associative}/multimap.bench.cpp | 2 +- .../benchmarks/containers/{ => associative}/multiset.bench.cpp | 0 .../test/benchmarks/containers/{ => associative}/set.bench.cpp | 0 .../containers/{ => associative}/unordered_map.bench.cpp | 2 +- .../containers/{ => associative}/unordered_multimap.bench.cpp | 2 +- .../containers/{ => associative}/unordered_multiset.bench.cpp | 0 .../containers/{ => associative}/unordered_set.bench.cpp | 0 10 files changed, 6 insertions(+), 6 deletions(-) rename libcxx/test/benchmarks/containers/{ => associative}/associative_container_benchmarks.h (99%) rename libcxx/test/benchmarks/containers/{ => associative}/flat_map.bench.cpp (97%) rename libcxx/test/benchmarks/containers/{ => associative}/map.bench.cpp (97%) rename libcxx/test/benchmarks/containers/{ => associative}/multimap.bench.cpp (97%) rename libcxx/test/benchmarks/containers/{ => associative}/multiset.bench.cpp (100%) rename libcxx/test/benchmarks/containers/{ => associative}/set.bench.cpp (100%) rename libcxx/test/benchmarks/containers/{ => associative}/unordered_map.bench.cpp (97%) rename libcxx/test/benchmarks/containers/{ => associative}/unordered_multimap.bench.cpp (97%) rename libcxx/test/benchmarks/containers/{ => associative}/unordered_multiset.bench.cpp (100%) rename libcxx/test/benchmarks/containers/{ => associative}/unordered_set.bench.cpp (100%) diff --git a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h similarity index 99% rename from libcxx/test/benchmarks/containers/associative_container_benchmarks.h rename to libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index 252b211e0456b..ddf4a5000324f 100644 --- a/libcxx/test/benchmarks/containers/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -16,7 +16,7 @@ #include #include "benchmark/benchmark.h" -#include "../GenerateInput.h" +#include "../../GenerateInput.h" namespace support { diff --git a/libcxx/test/benchmarks/containers/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp similarity index 97% rename from libcxx/test/benchmarks/containers/flat_map.bench.cpp rename to libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp index 013d1c4dcc464..d166eb2089e67 100644 --- a/libcxx/test/benchmarks/containers/flat_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp @@ -13,7 +13,7 @@ #include #include "associative_container_benchmarks.h" -#include "../GenerateInput.h" +#include "../../GenerateInput.h" #include "benchmark/benchmark.h" template diff --git a/libcxx/test/benchmarks/containers/map.bench.cpp b/libcxx/test/benchmarks/containers/associative/map.bench.cpp similarity index 97% rename from libcxx/test/benchmarks/containers/map.bench.cpp rename to libcxx/test/benchmarks/containers/associative/map.bench.cpp index 21d6af6af40c5..bd664dbb56ee7 100644 --- a/libcxx/test/benchmarks/containers/map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/map.bench.cpp @@ -13,7 +13,7 @@ #include #include "associative_container_benchmarks.h" -#include "../GenerateInput.h" +#include "../../GenerateInput.h" #include "benchmark/benchmark.h" template diff --git a/libcxx/test/benchmarks/containers/multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp similarity index 97% rename from libcxx/test/benchmarks/containers/multimap.bench.cpp rename to libcxx/test/benchmarks/containers/associative/multimap.bench.cpp index 1a983090d3826..15a0b573081bb 100644 --- a/libcxx/test/benchmarks/containers/multimap.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp @@ -12,7 +12,7 @@ #include #include "associative_container_benchmarks.h" -#include "../GenerateInput.h" +#include "../../GenerateInput.h" #include "benchmark/benchmark.h" template diff --git a/libcxx/test/benchmarks/containers/multiset.bench.cpp b/libcxx/test/benchmarks/containers/associative/multiset.bench.cpp similarity index 100% rename from libcxx/test/benchmarks/containers/multiset.bench.cpp rename to libcxx/test/benchmarks/containers/associative/multiset.bench.cpp diff --git a/libcxx/test/benchmarks/containers/set.bench.cpp b/libcxx/test/benchmarks/containers/associative/set.bench.cpp similarity index 100% rename from libcxx/test/benchmarks/containers/set.bench.cpp rename to libcxx/test/benchmarks/containers/associative/set.bench.cpp diff --git a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp similarity index 97% rename from libcxx/test/benchmarks/containers/unordered_map.bench.cpp rename to libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp index 707cc3e584462..66844a9ecdf79 100644 --- a/libcxx/test/benchmarks/containers/unordered_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp @@ -13,7 +13,7 @@ #include #include "associative_container_benchmarks.h" -#include "../GenerateInput.h" +#include "../../GenerateInput.h" #include "benchmark/benchmark.h" template diff --git a/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp similarity index 97% rename from libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp rename to libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp index ae7caf0f453c7..64ac8d4ee862f 100644 --- a/libcxx/test/benchmarks/containers/unordered_multimap.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp @@ -12,7 +12,7 @@ #include #include "associative_container_benchmarks.h" -#include "../GenerateInput.h" +#include "../../GenerateInput.h" #include "benchmark/benchmark.h" template diff --git a/libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_multiset.bench.cpp similarity index 100% rename from libcxx/test/benchmarks/containers/unordered_multiset.bench.cpp rename to libcxx/test/benchmarks/containers/associative/unordered_multiset.bench.cpp diff --git a/libcxx/test/benchmarks/containers/unordered_set.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_set.bench.cpp similarity index 100% rename from libcxx/test/benchmarks/containers/unordered_set.bench.cpp rename to libcxx/test/benchmarks/containers/associative/unordered_set.bench.cpp From 90b4ecd97ec965fd9bb48f9b6f1bd7d450ca2ede Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 15:27:03 -0500 Subject: [PATCH 08/16] Provide centralized control over the size of containers used --- .../associative_container_benchmarks.h | 173 ++++++++---------- 1 file changed, 74 insertions(+), 99 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index ddf4a5000324f..72aad6a35da75 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -54,6 +54,10 @@ void associative_container_benchmarks(std::string container) { auto get_key = [](Value const& v) { return adapt_operations::key_from_value(v); }; + auto bench = [&](std::string operation, auto f) { + benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(1024); + }; + // 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(). @@ -66,7 +70,7 @@ void associative_container_benchmarks(std::string container) { ///////////////////////// // Constructors ///////////////////////// - benchmark::RegisterBenchmark(container + "::ctor(const&)", [=](auto& st) { + bench("ctor(const&)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Container src(in.begin(), in.end()); @@ -85,9 +89,9 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (unsorted sequence)", [=](auto& st) { + bench("ctor(iterator, iterator) (unsorted sequence)", [=](auto& st) { const std::size_t size = st.range(0); std::mt19937 randomness; std::vector keys = generate_unique_keys(size); @@ -108,9 +112,9 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::ctor(iterator, iterator) (sorted sequence)", [=](auto& st) { + bench("ctor(iterator, iterator) (sorted sequence)", [=](auto& st) { const std::size_t size = st.range(0); std::vector keys = generate_unique_keys(size); std::sort(keys.begin(), keys.end()); @@ -130,12 +134,12 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); ///////////////////////// // Assignment ///////////////////////// - benchmark::RegisterBenchmark(container + "::operator=(const&)", [=](auto& st) { + bench("operator=(const&)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Container src(in.begin(), in.end()); @@ -154,12 +158,12 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); ///////////////////////// // Insertion ///////////////////////// - benchmark::RegisterBenchmark(container + "::insert(value) (already present)", [=](auto& st) { + bench("insert(value) (already present)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Value to_insert = in[in.size() / 2]; // pick any existing value @@ -184,9 +188,9 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::insert(value) (new value)", [=](auto& st) { + bench("insert(value) (new value)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size + 1)); Value to_insert = in.back(); @@ -206,12 +210,12 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); // The insert(hint, ...) methods are only relevant for ordered containers, and we lack // a good way to compute a hint for unordered ones. if constexpr (requires(Container c, Key k) { c.lower_bound(k); }) { - benchmark::RegisterBenchmark(container + "::insert(hint, value) (good hint)", [=](auto& st) { + bench("insert(hint, value) (good hint)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size + 1)); Value to_insert = in.back(); @@ -237,9 +241,9 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::insert(hint, value) (bad hint)", [=](auto& st) { + bench("insert(hint, value) (bad hint)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size + 1)); Value to_insert = in.back(); @@ -259,10 +263,10 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); } - benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (all new keys)", [=](auto& st) { + bench("insert(iterator, iterator) (all new keys)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size + (size / 10))); @@ -283,9 +287,9 @@ void associative_container_benchmarks(std::string container) { c = Container(small.begin(), small.end()); st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::insert(iterator, iterator) (half new keys)", [=](auto& st) { + bench("insert(iterator, iterator) (half new keys)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); @@ -306,12 +310,12 @@ void associative_container_benchmarks(std::string container) { c = Container(small.begin(), small.end()); st.ResumeTiming(); } - })->Arg(1024); + }); ///////////////////////// // Erasure ///////////////////////// - benchmark::RegisterBenchmark(container + "::erase(key) (existent)", [=](auto& st) { + bench("erase(key) (existent)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Value element = in[in.size() / 2]; // pick any element @@ -330,9 +334,9 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::erase(key) (non-existent)", [=](auto& st) { + bench("erase(key) (non-existent)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size + 1)); Value element = in.back(); @@ -348,9 +352,9 @@ void associative_container_benchmarks(std::string container) { // no cleanup required because we erased a non-existent element } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::erase(iterator)", [=](auto& st) { + bench("erase(iterator)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Value element = in[in.size() / 2]; // pick any element @@ -375,9 +379,9 @@ void associative_container_benchmarks(std::string container) { } st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::erase(iterator, iterator) (erase half the container)", [=](auto& st) { + bench("erase(iterator, iterator) (erase half the container)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Container c(in.begin(), in.end()); @@ -395,9 +399,9 @@ void associative_container_benchmarks(std::string container) { last = std::next(c.begin(), 3 * (c.size() / 4)); st.ResumeTiming(); } - })->Arg(1024); + }); - benchmark::RegisterBenchmark(container + "::clear()", [=](auto& st) { + bench("clear()", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Container c(in.begin(), in.end()); @@ -411,12 +415,12 @@ void associative_container_benchmarks(std::string container) { c = Container(in.begin(), in.end()); st.ResumeTiming(); } - })->Arg(1024); + }); ///////////////////////// // Capacity ///////////////////////// - benchmark::RegisterBenchmark(container + "::size()", [=](auto& st) { + bench("size()", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); Container c(in.begin(), in.end()); @@ -427,7 +431,7 @@ void associative_container_benchmarks(std::string container) { benchmark::DoNotOptimize(res); benchmark::ClobberMemory(); } - })->Arg(1024); + }); ///////////////////////// // Query @@ -469,75 +473,46 @@ void associative_container_benchmarks(std::string container) { }; }; - benchmark::RegisterBenchmark( - container + "::find(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::find(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })) - ->Arg(1024); - - benchmark::RegisterBenchmark( - container + "::count(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::count(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); })) - ->Arg(1024); - - benchmark::RegisterBenchmark( - container + "::contains(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { return c.contains(get_key(element)); })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::contains(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.contains(get_key(element)); - })) - ->Arg(1024); + bench("find(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })); + bench("find(key) (non-existent)", + bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })); + + bench("count(key) (existent)", + bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); })); + bench("count(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.count(get_key(element)); + })); + + bench("contains(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { + return c.contains(get_key(element)); + })); + bench("contains(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.contains(get_key(element)); + })); // Only for ordered containers - if constexpr (requires(Container c, Key key) { c.lower_bound(key); }) { - benchmark::RegisterBenchmark( - container + "::lower_bound(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { - return c.lower_bound(get_key(element)); - })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::lower_bound(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.lower_bound(get_key(element)); - })) - ->Arg(1024); - - benchmark::RegisterBenchmark( - container + "::upper_bound(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { - return c.upper_bound(get_key(element)); - })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::upper_bound(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.upper_bound(get_key(element)); - })) - ->Arg(1024); - - benchmark::RegisterBenchmark( - container + "::equal_range(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { - return c.equal_range(get_key(element)); - })) - ->Arg(1024); - benchmark::RegisterBenchmark( - container + "::equal_range(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.equal_range(get_key(element)); - })) - ->Arg(1024); + if constexpr (requires(Container c, Key k) { c.lower_bound(k); }) { + bench("lower_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { + return c.lower_bound(get_key(element)); + })); + bench("lower_bound(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.lower_bound(get_key(element)); + })); + + bench("upper_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { + return c.upper_bound(get_key(element)); + })); + bench("upper_bound(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.upper_bound(get_key(element)); + })); + + bench("equal_range(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { + return c.equal_range(get_key(element)); + })); + bench("equal_range(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { + return c.equal_range(get_key(element)); + })); } } From 658cd30c7e28808e69fef9232105ca71b7cdc3e4 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 15:53:12 -0500 Subject: [PATCH 09/16] Call DoNotOptimize on the result of operations --- .../associative_container_benchmarks.h | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index 72aad6a35da75..3c29c751dc1cc 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -171,7 +171,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].insert(to_insert); + auto result = c[i].insert(to_insert); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } @@ -199,7 +200,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].insert(to_insert); + auto result = c[i].insert(to_insert); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } @@ -229,7 +231,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].insert(hints[i], to_insert); + auto result = c[i].insert(hints[i], to_insert); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } @@ -252,7 +255,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].insert(c[i].begin(), to_insert); + auto result = c[i].insert(c[i].begin(), to_insert); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } @@ -323,7 +327,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].erase(get_key(element)); + auto result = c[i].erase(get_key(element)); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } @@ -345,7 +350,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c.erase(get_key(element)); + auto result = c.erase(get_key(element)); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c); benchmark::ClobberMemory(); } @@ -368,7 +374,8 @@ void associative_container_benchmarks(std::string container) { while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - c[i].erase(iterators[i]); + auto result = c[i].erase(iterators[i]); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } @@ -389,7 +396,8 @@ void associative_container_benchmarks(std::string container) { auto first = std::next(c.begin(), c.size() / 4); auto last = std::next(c.begin(), 3 * (c.size() / 4)); for (auto _ : st) { - c.erase(first, last); + auto result = c.erase(first, last); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c); benchmark::ClobberMemory(); @@ -426,9 +434,9 @@ void associative_container_benchmarks(std::string container) { Container c(in.begin(), in.end()); for (auto _ : st) { - auto res = c.size(); + auto result = c.size(); + benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c); - benchmark::DoNotOptimize(res); benchmark::ClobberMemory(); } }); From a4546d85d513641f2d19078176d0c962b1db053e Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 16:26:46 -0500 Subject: [PATCH 10/16] Adjust batch size and run for a few container sizes --- .../containers/associative/associative_container_benchmarks.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index 3c29c751dc1cc..10e21eb2f9370 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -55,13 +55,13 @@ void associative_container_benchmarks(std::string container) { auto get_key = [](Value const& v) { return adapt_operations::key_from_value(v); }; auto bench = [&](std::string operation, auto f) { - benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(1024); + benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(32)->Arg(1024)->Arg(8192); }; // 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 = 10; + static constexpr std::size_t BatchSize = 32; struct alignas(Container) ScratchSpace { char storage[sizeof(Container)]; From 9f109f8ee1eba29a98bab68d75208107fccc7682 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Wed, 5 Feb 2025 18:12:00 -0500 Subject: [PATCH 11/16] Some tweaks to reduce variance --- .../associative_container_benchmarks.h | 32 +++++++++++-------- .../containers/associative/flat_map.bench.cpp | 1 - .../containers/associative/map.bench.cpp | 1 - .../containers/associative/multimap.bench.cpp | 1 - 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index 10e21eb2f9370..53ff5790a2f49 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include "benchmark/benchmark.h" @@ -58,6 +60,12 @@ void associative_container_benchmarks(std::string container) { benchmark::RegisterBenchmark(container + "::" + operation, f)->Arg(32)->Arg(1024)->Arg(8192); }; + static constexpr bool is_multi_key_container = + !std::is_same_v::InsertionResult, + std::pair>; + + static constexpr bool is_ordered_container = requires(Container c, Key k) { c.lower_bound(k); }; + // 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(). @@ -168,26 +176,23 @@ void associative_container_benchmarks(std::string container) { std::vector in = make_value_types(generate_unique_keys(size)); Value to_insert = in[in.size() / 2]; // pick any existing value std::vector c(BatchSize, Container(in.begin(), in.end())); + typename Container::iterator inserted[BatchSize]; while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - auto result = c[i].insert(to_insert); - benchmark::DoNotOptimize(result); + inserted[i] = adapt_operations::get_iterator(c[i].insert(to_insert)); + benchmark::DoNotOptimize(inserted[i]); benchmark::DoNotOptimize(c[i]); benchmark::ClobberMemory(); } - st.PauseTiming(); - // Unique-key containers will not insert above, but multi-key containers will insert - // the key a second time. Restore the invariant that there's a single copy of each - // key in the container. - for (std::size_t i = 0; i != BatchSize; ++i) { - auto const& key = get_key(to_insert); - if (c[i].count(key) > 1) { - c[i].erase(key); + if constexpr (is_multi_key_container) { + st.PauseTiming(); + for (std::size_t i = 0; i != BatchSize; ++i) { + c[i].erase(inserted[i]); } + st.ResumeTiming(); } - st.ResumeTiming(); } }); @@ -216,7 +221,7 @@ void associative_container_benchmarks(std::string container) { // The insert(hint, ...) methods are only relevant for ordered containers, and we lack // a good way to compute a hint for unordered ones. - if constexpr (requires(Container c, Key k) { c.lower_bound(k); }) { + if constexpr (is_ordered_container) { bench("insert(hint, value) (good hint)", [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size + 1)); @@ -499,8 +504,7 @@ void associative_container_benchmarks(std::string container) { return c.contains(get_key(element)); })); - // Only for ordered containers - if constexpr (requires(Container c, Key k) { c.lower_bound(k); }) { + if constexpr (is_ordered_container) { bench("lower_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { return c.lower_bound(get_key(element)); })); diff --git a/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp index d166eb2089e67..17f84370901bc 100644 --- a/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp @@ -29,7 +29,6 @@ struct support::adapt_operations> { int main(int argc, char** argv) { support::associative_container_benchmarks>("std::flat_map"); - support::associative_container_benchmarks>("std::flat_map"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); diff --git a/libcxx/test/benchmarks/containers/associative/map.bench.cpp b/libcxx/test/benchmarks/containers/associative/map.bench.cpp index bd664dbb56ee7..cee669ae0a667 100644 --- a/libcxx/test/benchmarks/containers/associative/map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/map.bench.cpp @@ -29,7 +29,6 @@ struct support::adapt_operations> { int main(int argc, char** argv) { support::associative_container_benchmarks>("std::map"); - support::associative_container_benchmarks>("std::map"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); diff --git a/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp index 15a0b573081bb..6ae93f06aa363 100644 --- a/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/multimap.bench.cpp @@ -28,7 +28,6 @@ struct support::adapt_operations> { int main(int argc, char** argv) { support::associative_container_benchmarks>("std::multimap"); - support::associative_container_benchmarks>("std::multimap"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); From 4972089871ad9adfc67f407ed482187cbb08f0d9 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 6 Feb 2025 08:19:18 -0500 Subject: [PATCH 12/16] Remove a string benchmark for multimap --- .../benchmarks/containers/associative/unordered_map.bench.cpp | 3 --- .../containers/associative/unordered_multimap.bench.cpp | 3 --- 2 files changed, 6 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp index 66844a9ecdf79..57adec2d214d4 100644 --- a/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/unordered_map.bench.cpp @@ -8,7 +8,6 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 -#include #include #include @@ -29,8 +28,6 @@ struct support::adapt_operations> { int main(int argc, char** argv) { support::associative_container_benchmarks>("std::unordered_map"); - support::associative_container_benchmarks>( - "std::unordered_map"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); diff --git a/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp index 64ac8d4ee862f..8738ca4bf9f0c 100644 --- a/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/unordered_multimap.bench.cpp @@ -8,7 +8,6 @@ // UNSUPPORTED: c++03, c++11, c++14, c++17 -#include #include #include "associative_container_benchmarks.h" @@ -28,8 +27,6 @@ struct support::adapt_operations> { int main(int argc, char** argv) { support::associative_container_benchmarks>("std::unordered_multimap"); - support::associative_container_benchmarks>( - "std::unordered_multimap"); benchmark::Initialize(&argc, argv); benchmark::RunSpecifiedBenchmarks(); From 4faf8875fcec562e8a1a56bb4ef1e751cbe6bb78 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 6 Feb 2025 09:08:56 -0500 Subject: [PATCH 13/16] Reduce the variance in lookup benchmarks --- .../associative_container_benchmarks.h | 89 +++++++++---------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index 53ff5790a2f49..dca2592ea7051 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -348,14 +348,17 @@ void associative_container_benchmarks(std::string container) { bench("erase(key) (non-existent)", [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = make_value_types(generate_unique_keys(size + 1)); - Value element = in.back(); - in.pop_back(); + std::vector in = make_value_types(generate_unique_keys(size + BatchSize)); + std::vector keys; + for (std::size_t i = 0; i != BatchSize; ++i) { + keys.push_back(get_key(in.back())); + in.pop_back(); + } Container c(in.begin(), in.end()); while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - auto result = c.erase(get_key(element)); + auto result = c.erase(keys[i]); benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c); benchmark::ClobberMemory(); @@ -449,16 +452,20 @@ void associative_container_benchmarks(std::string container) { ///////////////////////// // Query ///////////////////////// - auto bench_with_existent_key = [=](auto func) { + auto with_existent_key = [=](auto func) { return [=](auto& st) { const std::size_t size = st.range(0); std::vector in = make_value_types(generate_unique_keys(size)); - Value element = in[in.size() / 2]; // pick any element + // Pick any `BatchSize` number of elements + std::vector keys; + for (std::size_t i = 0; i < in.size(); i += (in.size() / BatchSize)) { + keys.push_back(get_key(in.at(i))); + } Container c(in.begin(), in.end()); while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - auto result = func(c, element); + auto result = func(c, keys[i]); benchmark::DoNotOptimize(c); benchmark::DoNotOptimize(result); benchmark::ClobberMemory(); @@ -467,17 +474,20 @@ void associative_container_benchmarks(std::string container) { }; }; - auto bench_with_nonexistent_key = [=](auto func) { + auto with_nonexistent_key = [=](auto func) { return [=](auto& st) { const std::size_t size = st.range(0); - std::vector in = make_value_types(generate_unique_keys(size + 1)); - Value element = in.back(); - in.pop_back(); + std::vector in = make_value_types(generate_unique_keys(size + BatchSize)); + std::vector keys; + for (std::size_t i = 0; i != BatchSize; ++i) { + keys.push_back(get_key(in.back())); + in.pop_back(); + } Container c(in.begin(), in.end()); while (st.KeepRunningBatch(BatchSize)) { for (std::size_t i = 0; i != BatchSize; ++i) { - auto result = func(c, element); + auto result = func(c, keys[i]); benchmark::DoNotOptimize(c); benchmark::DoNotOptimize(result); benchmark::ClobberMemory(); @@ -486,45 +496,30 @@ void associative_container_benchmarks(std::string container) { }; }; - bench("find(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })); - bench("find(key) (non-existent)", - bench_with_nonexistent_key([=](Container const& c, Value const& element) { return c.find(get_key(element)); })); + auto find = [](Container const& c, Key const& key) { return c.find(key); }; + bench("find(key) (existent)", with_existent_key(find)); + bench("find(key) (non-existent)", with_nonexistent_key(find)); - bench("count(key) (existent)", - bench_with_existent_key([=](Container const& c, Value const& element) { return c.count(get_key(element)); })); - bench("count(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.count(get_key(element)); - })); + auto count = [](Container const& c, Key const& key) { return c.count(key); }; + bench("count(key) (existent)", with_existent_key(count)); + bench("count(key) (non-existent)", with_nonexistent_key(count)); - bench("contains(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { - return c.contains(get_key(element)); - })); - bench("contains(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.contains(get_key(element)); - })); + auto contains = [](Container const& c, Key const& key) { return c.contains(key); }; + bench("contains(key) (existent)", with_existent_key(contains)); + bench("contains(key) (non-existent)", with_nonexistent_key(contains)); if constexpr (is_ordered_container) { - bench("lower_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { - return c.lower_bound(get_key(element)); - })); - bench("lower_bound(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.lower_bound(get_key(element)); - })); - - bench("upper_bound(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { - return c.upper_bound(get_key(element)); - })); - bench("upper_bound(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.upper_bound(get_key(element)); - })); - - bench("equal_range(key) (existent)", bench_with_existent_key([=](Container const& c, Value const& element) { - return c.equal_range(get_key(element)); - })); - bench("equal_range(key) (non-existent)", bench_with_nonexistent_key([=](Container const& c, Value const& element) { - return c.equal_range(get_key(element)); - })); + auto lower_bound = [](Container const& c, Key const& key) { return c.lower_bound(key); }; + bench("lower_bound(key) (existent)", with_existent_key(lower_bound)); + bench("lower_bound(key) (non-existent)", with_nonexistent_key(lower_bound)); + + auto upper_bound = [](Container const& c, Key const& key) { return c.upper_bound(key); }; + bench("upper_bound(key) (existent)", with_existent_key(upper_bound)); + bench("upper_bound(key) (non-existent)", with_nonexistent_key(upper_bound)); + + auto equal_range = [](Container const& c, Key const& key) { return c.equal_range(key); }; + bench("equal_range(key) (existent)", with_existent_key(equal_range)); + bench("equal_range(key) (non-existent)", with_nonexistent_key(equal_range)); } } From 20de668b61f483011448e0462b86438c0ecbb4ec Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 6 Feb 2025 12:40:25 -0500 Subject: [PATCH 14/16] Add flat_multimap benchmarks --- .../containers/associative/flat_map.bench.cpp | 5 ++- .../associative/flat_multimap.bench.cpp | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 libcxx/test/benchmarks/containers/associative/flat_multimap.bench.cpp diff --git a/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp index 17f84370901bc..82902d50f31e6 100644 --- a/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp +++ b/libcxx/test/benchmarks/containers/associative/flat_map.bench.cpp @@ -9,7 +9,6 @@ // REQUIRES: std-at-least-c++26 #include -#include #include #include "associative_container_benchmarks.h" @@ -18,8 +17,8 @@ template struct support::adapt_operations> { - using ValueType = typename std::map::value_type; - using KeyType = typename std::map::key_type; + using ValueType = typename std::flat_map::value_type; + using KeyType = typename std::flat_map::key_type; static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } static KeyType key_from_value(ValueType const& value) { return value.first; } diff --git a/libcxx/test/benchmarks/containers/associative/flat_multimap.bench.cpp b/libcxx/test/benchmarks/containers/associative/flat_multimap.bench.cpp new file mode 100644 index 0000000000000..f752f79b3b454 --- /dev/null +++ b/libcxx/test/benchmarks/containers/associative/flat_multimap.bench.cpp @@ -0,0 +1,35 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 + +#include + +#include "associative_container_benchmarks.h" +#include "../../GenerateInput.h" +#include "benchmark/benchmark.h" + +template +struct support::adapt_operations> { + using ValueType = typename std::flat_multimap::value_type; + using KeyType = typename std::flat_multimap::key_type; + static ValueType value_from_key(KeyType const& k) { return {k, Generate::arbitrary()}; } + static KeyType key_from_value(ValueType const& value) { return value.first; } + + using InsertionResult = typename std::flat_multimap::iterator; + static auto get_iterator(InsertionResult const& result) { return result; } +}; + +int main(int argc, char** argv) { + support::associative_container_benchmarks>("std::flat_multimap"); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); + return 0; +} From a3a126d7285e3aecc91ea5418c02d6411ab0ed5f Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 6 Feb 2025 12:45:11 -0500 Subject: [PATCH 15/16] Remove size() benchmark since complexity is constant, not very interesting --- .../associative_container_benchmarks.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index dca2592ea7051..b9c710a5887cb 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -433,22 +433,6 @@ void associative_container_benchmarks(std::string container) { } }); - ///////////////////////// - // Capacity - ///////////////////////// - bench("size()", [=](auto& st) { - const std::size_t size = st.range(0); - std::vector in = make_value_types(generate_unique_keys(size)); - Container c(in.begin(), in.end()); - - for (auto _ : st) { - auto result = c.size(); - benchmark::DoNotOptimize(result); - benchmark::DoNotOptimize(c); - benchmark::ClobberMemory(); - } - }); - ///////////////////////// // Query ///////////////////////// From a38107a543ca77fbc2103cb35abe1494202ce072 Mon Sep 17 00:00:00 2001 From: Louis Dionne Date: Thu, 6 Feb 2025 14:38:38 -0500 Subject: [PATCH 16/16] [[maybe_unused]] --- .../associative/associative_container_benchmarks.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h index b9c710a5887cb..fb4455c4aa9da 100644 --- a/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h +++ b/libcxx/test/benchmarks/containers/associative/associative_container_benchmarks.h @@ -287,7 +287,7 @@ void associative_container_benchmarks(std::string container) { } Container c(small.begin(), small.end()); - for (auto _ : st) { + for ([[maybe_unused]] auto _ : st) { c.insert(in.begin(), in.end()); benchmark::DoNotOptimize(c); benchmark::ClobberMemory(); @@ -310,7 +310,7 @@ void associative_container_benchmarks(std::string container) { } Container c(small.begin(), small.end()); - for (auto _ : st) { + for ([[maybe_unused]] auto _ : st) { c.insert(in.begin(), in.end()); benchmark::DoNotOptimize(c); benchmark::ClobberMemory(); @@ -403,7 +403,7 @@ void associative_container_benchmarks(std::string container) { auto first = std::next(c.begin(), c.size() / 4); auto last = std::next(c.begin(), 3 * (c.size() / 4)); - for (auto _ : st) { + for ([[maybe_unused]] auto _ : st) { auto result = c.erase(first, last); benchmark::DoNotOptimize(result); benchmark::DoNotOptimize(c); @@ -422,7 +422,7 @@ void associative_container_benchmarks(std::string container) { std::vector in = make_value_types(generate_unique_keys(size)); Container c(in.begin(), in.end()); - for (auto _ : st) { + for ([[maybe_unused]] auto _ : st) { c.clear(); benchmark::DoNotOptimize(c); benchmark::ClobberMemory();