From d298ae4c15eaa195486a61b32034cc8a5f7739dc Mon Sep 17 00:00:00 2001 From: Grey Golla Date: Wed, 10 Jul 2024 13:24:19 -0700 Subject: [PATCH 1/4] Add runtime benchmarks for `Fixed*Map` data structures This adds a new set of tests tagged "benchmark" in bazel, which are filtered out of the default `test_suite` target. This means that they will only be built/run manually. --- BUILD.bazel | 65 ++++++++++++++---- CMakeLists.txt | 9 ++- test/benchmarks/map_clear.cpp | 113 ++++++++++++++++++++++++++++++++ test/benchmarks/map_copy.cpp | 116 +++++++++++++++++++++++++++++++++ test/benchmarks/map_lookup.cpp | 116 +++++++++++++++++++++++++++++++++ test/benchmarks/map_utils.hpp | 90 +++++++++++++++++++++++++ test/fixed_map_perf_test.cpp | 89 ------------------------- test/fixed_map_test.cpp | 55 ++++++++++++++++ 8 files changed, 548 insertions(+), 105 deletions(-) create mode 100644 test/benchmarks/map_clear.cpp create mode 100644 test/benchmarks/map_copy.cpp create mode 100644 test/benchmarks/map_lookup.cpp create mode 100644 test/benchmarks/map_utils.hpp delete mode 100644 test/fixed_map_perf_test.cpp diff --git a/BUILD.bazel b/BUILD.bazel index 4f176247..1af7d9e6 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1155,20 +1155,6 @@ cc_test( copts = ["-std=c++20"], ) -cc_test( - name = "fixed_map_perf_test", - srcs = ["test/fixed_map_perf_test.cpp"], - deps = [ - ":consteval_compare", - ":fixed_index_based_storage", - ":fixed_map", - ":fixed_red_black_tree", - "@com_google_googletest//:gtest_main", - "@com_google_benchmark//:benchmark_main", - ], - copts = ["-std=c++20"], -) - cc_test( name = "fixed_list_test", srcs = ["test/fixed_list_test.cpp"], @@ -1612,6 +1598,57 @@ cc_test( copts = ["-std=c++20"], ) +cc_library( + name = "benchmark_map_utils", + hdrs = ["test/benchmarks/map_utils.hpp"], +) + +cc_test( + name = "map_copy_bench", + srcs = ["test/benchmarks/map_copy.cpp"], + deps = [ + ":consteval_compare", + ":fixed_map", + ":fixed_unordered_map", + ":mock_testing_types", + ":benchmark_map_utils", + "@com_google_benchmark//:benchmark_main", + ], + copts = ["-std=c++20"], + tags = ["benchmark"], +) + +cc_test( + name = "map_lookup_bench", + srcs = ["test/benchmarks/map_lookup.cpp"], + deps = [ + ":consteval_compare", + ":fixed_map", + ":fixed_unordered_map", + ":mock_testing_types", + ":benchmark_map_utils", + "@com_google_benchmark//:benchmark_main", + ], + copts = ["-std=c++20"], + tags = ["benchmark"], +) + +cc_test( + name = "map_clear_bench", + srcs = ["test/benchmarks/map_clear.cpp"], + deps = [ + ":consteval_compare", + ":fixed_map", + ":fixed_unordered_map", + ":mock_testing_types", + ":benchmark_map_utils", + "@com_google_benchmark//:benchmark_main", + ], + copts = ["-std=c++20"], + tags = ["benchmark"], +) + test_suite( name = "all_tests", + tags = ["-benchmark"], ) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc900dd9..bbf53a7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,8 +167,6 @@ if(BUILD_TESTS) add_test_dependencies(fixed_list_test) add_executable(fixed_map_test test/fixed_map_test.cpp) add_test_dependencies(fixed_map_test) - add_executable(fixed_map_perf_test test/fixed_map_perf_test.cpp) - add_test_dependencies(fixed_map_perf_test) add_executable(fixed_red_black_tree_test test/fixed_red_black_tree_test.cpp) add_test_dependencies(fixed_red_black_tree_test) add_executable(fixed_red_black_tree_view_test test/fixed_red_black_tree_view_test.cpp) @@ -227,6 +225,13 @@ if(BUILD_TESTS) add_test_dependencies(tuples_test) add_executable(type_name_test test/type_name_test.cpp) add_test_dependencies(type_name_test) + + add_executable(map_copy_bench test/benchmarks/map_copy.cpp) + add_test_dependencies(map_copy_bench) + add_executable(map_lookup_bench test/benchmarks/map_lookup.cpp) + add_test_dependencies(map_lookup_bench) + add_executable(map_clear_bench test/benchmarks/map_clear.cpp) + add_test_dependencies(map_clear_bench) endif() option(FIXED_CONTAINERS_OPT_INSTALL "Enable install target" ${PROJECT_IS_TOP_LEVEL}) diff --git a/test/benchmarks/map_clear.cpp b/test/benchmarks/map_clear.cpp new file mode 100644 index 00000000..cf5ac15e --- /dev/null +++ b/test/benchmarks/map_clear.cpp @@ -0,0 +1,113 @@ +#include "fixed_containers/fixed_map.hpp" +#include "fixed_containers/fixed_unordered_map.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace fixed_containers +{ + +namespace +{ +template +void benchmark_map_copy(benchmark::State& state) +{ + const int64_t nelem = state.range(0); + MapType instance = {}; + + using KeyType = typename MapType::key_type; + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{instance}; + benchmark::DoNotOptimize(instance2); + } +} + +template +void benchmark_map_copy_then_clear(benchmark::State& state) +{ + using KeyType = typename MapType::key_type; + MapType instance{}; + const int64_t nelem = state.range(0); + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{instance}; + instance2.clear(); + benchmark::DoNotOptimize(instance2); + } +} + +template +void benchmark_map_copy_then_reconstruct(benchmark::State& state) +{ + using KeyType = typename MapType::key_type; + MapType instance{}; + const int64_t nelem = state.range(0); + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{instance}; + instance2 = {}; + benchmark::DoNotOptimize(instance2); + } +} + +template +void benchmark_array_clear(benchmark::State& state) +{ + ArrType instance{}; + + for (auto _ : state) + { + instance.fill(0); + benchmark::DoNotOptimize(instance); + } +} +} // namespace + +constexpr std::size_t MAX_SIZE = 8 << 13; + +BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_clear>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_reconstruct>)->Range(16, MAX_SIZE); + +BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_clear>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_reconstruct>)->Range(16, MAX_SIZE); + +BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_clear>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_reconstruct>)->Range(16, MAX_SIZE); + +BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_clear>) + ->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy_then_reconstruct>) + ->Range(16, MAX_SIZE); + +// more-or-less the theoretical best performance we could possibly get for a full FixedUnorderedMap +// (just 0 out every bucket) +BENCHMARK(benchmark_array_clear>); + +} // namespace fixed_containers + +BENCHMARK_MAIN(); diff --git a/test/benchmarks/map_copy.cpp b/test/benchmarks/map_copy.cpp new file mode 100644 index 00000000..dd2ea125 --- /dev/null +++ b/test/benchmarks/map_copy.cpp @@ -0,0 +1,116 @@ +#include "map_utils.hpp" + +#include "../mock_testing_types.hpp" +#include "fixed_containers/fixed_unordered_map.hpp" + +#include + +#include + +namespace fixed_containers +{ + +namespace +{ +template +void benchmark_map_copy_fresh(benchmark::State& state) +{ + const int64_t nelem = state.range(0); + MapType instance = {}; + + using KeyType = typename MapType::key_type; + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{instance}; + benchmark::DoNotOptimize(instance2); + } +} + +template +void benchmark_map_iterate_copy_fresh(benchmark::State& state) +{ + const int64_t nelem = state.range(0); + MapType instance = {}; + + using KeyType = typename MapType::key_type; + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{}; + for (auto elem : instance) + { + instance2.try_emplace(elem.first, elem.second); + } + benchmark::DoNotOptimize(instance2); + } +} + +template +void benchmark_map_copy_shuffled(benchmark::State& state) +{ + const int64_t nelem = state.range(0); + auto instance = map_benchmarks::make_shuffled_map(); + + using KeyType = typename MapType::key_type; + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{instance}; + benchmark::DoNotOptimize(instance2); + } +} + +template +void benchmark_map_iterate_copy_shuffled(benchmark::State& state) +{ + const int64_t nelem = state.range(0); + auto instance = map_benchmarks::make_shuffled_map(); + + using KeyType = typename MapType::key_type; + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + MapType instance2{}; + for (auto elem : instance) + { + instance2.try_emplace(elem.first, elem.second); + } + benchmark::DoNotOptimize(instance2); + } +} +} // namespace + +BENCHMARK( + benchmark_map_copy_fresh>) + ->DenseRange(1024, 8 << 14, 1024); +BENCHMARK(benchmark_map_iterate_copy_fresh< + FixedUnorderedMap>) + ->DenseRange(1024, 8 << 14, 1024); + +BENCHMARK( + benchmark_map_copy_shuffled>) + ->DenseRange(1024, 8 << 14, 1024); +BENCHMARK(benchmark_map_iterate_copy_shuffled< + FixedUnorderedMap>) + ->DenseRange(1024, 8 << 14, 1024); + +} // namespace fixed_containers + +BENCHMARK_MAIN(); diff --git a/test/benchmarks/map_lookup.cpp b/test/benchmarks/map_lookup.cpp new file mode 100644 index 00000000..4e10be8e --- /dev/null +++ b/test/benchmarks/map_lookup.cpp @@ -0,0 +1,116 @@ +#include "map_utils.hpp" + +#include "fixed_containers/fixed_map.hpp" +#include "fixed_containers/fixed_unordered_map.hpp" + +#include + +#include +#include +#include + +namespace fixed_containers +{ +namespace +{ +template +void benchmark_map_lookup_fresh(benchmark::State& state) +{ + using KeyType = typename MapType::key_type; + MapType instance{}; + const int64_t nelem = state.range(0); + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + for (int64_t i = 0; i < nelem; i += nelem / 8) + { + auto& entry = instance.at(static_cast(i)); + benchmark::DoNotOptimize(entry); + } + } +} + +template +void benchmark_map_lookup_shuffled(benchmark::State& state) +{ + using KeyType = typename MapType::key_type; + auto instance = map_benchmarks::make_shuffled_map(); + const int64_t nelem = state.range(0); + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + for (int64_t i = 0; i < nelem; i += nelem / 8) + { + auto& entry = instance.at(static_cast(i)); + benchmark::DoNotOptimize(entry); + } + } +} + +template +void benchmark_map_iterate_fresh(benchmark::State& state) +{ + using KeyType = typename MapType::key_type; + MapType instance{}; + const int64_t nelem = state.range(0); + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + for (auto pair : instance) + { + benchmark::DoNotOptimize(pair.second); + } + } +} + +template +void benchmark_map_iterate_shuffled(benchmark::State& state) +{ + using KeyType = typename MapType::key_type; + auto instance = map_benchmarks::make_shuffled_map(); + const int64_t nelem = state.range(0); + for (int64_t i = 0; i < nelem; i++) + { + instance.try_emplace(static_cast(i)); + } + + for (auto _ : state) + { + for (auto pair : instance) + { + benchmark::DoNotOptimize(pair.second); + } + } +} +} // namespace + +BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_lookup_shuffled>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_lookup_shuffled>)->Range(256, 8 << 14); + +BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_iterate_shuffled>)->Range(256, 8 << 14); +BENCHMARK(benchmark_map_iterate_shuffled>) + ->Range(256, 8 << 14); + +} // namespace fixed_containers + +BENCHMARK_MAIN(); diff --git a/test/benchmarks/map_utils.hpp b/test/benchmarks/map_utils.hpp new file mode 100644 index 00000000..52df78b0 --- /dev/null +++ b/test/benchmarks/map_utils.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include +#include + +namespace fixed_containers::map_benchmarks +{ + +template +[[maybe_unused]] static void del(MapType& map, int64_t divisor) +{ + auto iter = map.begin(); + while (iter != map.end()) + { + if (iter->first % divisor == 0) + { + iter = map.erase(iter); + } + else + { + iter++; + } + } +} + +template +[[maybe_unused]] static void replace_low(MapType& map, std::size_t divisor) +{ + using KeyType = typename MapType::key_type; + for (std::size_t i = 0; i < map.max_size(); i += divisor) + { + map.try_emplace(static_cast(i)); + } +} + +template +[[maybe_unused]] static void replace_high(MapType& map, std::size_t divisor) +{ + using KeyType = typename MapType::key_type; + // find the largest multiple smaller than `n` + const std::size_t start = ((map.max_size() - 1) / divisor) * divisor; + + for (std::size_t i = start; i > 0; i -= divisor) + { + map.try_emplace(static_cast(i)); + } +} + +// create a "well-used" map, so that new elements will be inserted into dispersed spots in the map +// instead of spots with good memory locality +template +[[maybe_unused]] static MapType make_shuffled_map() +{ + using KeyType = typename MapType::key_type; + MapType instance{}; + // fill the map completely + for (std::size_t i = 0; i < instance.max_size(); i++) + { + instance.try_emplace(static_cast(i)); + } + + // delete and replace chunks of the map + del(instance, 2); + del(instance, 5); + del(instance, 227); + replace_low(instance, 5); + replace_high(instance, 2); + replace_low(instance, 227); + del(instance, 13); + del(instance, 21); + del(instance, 31); + replace_high(instance, 21); + replace_low(instance, 13); + replace_high(instance, 31); + del(instance, 3); + del(instance, 7); + replace_low(instance, 3); + replace_high(instance, 7); + + // clear the map + del(instance, 997); + del(instance, 333); + del(instance, 1023); + del(instance, 15); + del(instance, 1); + + return instance; +} + +} // namespace fixed_containers::map_benchmarks diff --git a/test/fixed_map_perf_test.cpp b/test/fixed_map_perf_test.cpp deleted file mode 100644 index 5ede12cc..00000000 --- a/test/fixed_map_perf_test.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "fixed_containers/consteval_compare.hpp" -#include "fixed_containers/fixed_index_based_storage.hpp" -#include "fixed_containers/fixed_map.hpp" -#include "fixed_containers/fixed_red_black_tree_nodes.hpp" - -#include - -#include -#include -#include -#include -#include - -namespace fixed_containers -{ -namespace -{ -using V = std::array, 30>; -constexpr std::size_t CAP = 130; - -template -using CompactPoolFixedMap = - FixedMap, - fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, - FixedIndexBasedPoolStorage>; -static_assert(std::is_same_v, CompactPoolFixedMap>); - -template -using CompactContiguousFixedMap = - FixedMap, - fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, - FixedIndexBasedContiguousStorage>; - -template -using DedicatedColorBitPoolFixedMap = - FixedMap, - fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::DEDICATED_COLOR, - FixedIndexBasedContiguousStorage>; - -template -using DedicatedColorBitContiguousFixedMap = - FixedMap, - fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::DEDICATED_COLOR, - FixedIndexBasedContiguousStorage>; - -// The reference boost-based fixed_map (with an array-backed pool-allocator) was at 51000 -// at the time of writing. -static_assert(consteval_compare::equal<50992, sizeof(FixedMap)>); -static_assert(consteval_compare::equal<50992, sizeof(CompactPoolFixedMap)>); -static_assert(consteval_compare::equal<50992, sizeof(CompactContiguousFixedMap)>); -static_assert(consteval_compare::equal<52032, sizeof(DedicatedColorBitPoolFixedMap)>); -static_assert( - consteval_compare::equal<52032, sizeof(DedicatedColorBitContiguousFixedMap)>); - -template -void benchmark_map_lookup(benchmark::State& state) -{ - using KeyType = typename MapType::key_type; - MapType instance{}; - for (std::size_t i = 0; i < 100; i++) - { - instance.try_emplace(static_cast(i)); - } - - for (auto _ : state) - { - auto& entry = instance.at(7); - benchmark::DoNotOptimize(entry); - } -} - -BENCHMARK(benchmark_map_lookup>); -BENCHMARK(benchmark_map_lookup>); -} // namespace -} // namespace fixed_containers - -BENCHMARK_MAIN(); diff --git a/test/fixed_map_test.cpp b/test/fixed_map_test.cpp index 6a23f649..dde08340 100644 --- a/test/fixed_map_test.cpp +++ b/test/fixed_map_test.cpp @@ -8,6 +8,8 @@ #include "fixed_containers/assert_or_abort.hpp" #include "fixed_containers/concepts.hpp" #include "fixed_containers/consteval_compare.hpp" +#include "fixed_containers/fixed_index_based_storage.hpp" +#include "fixed_containers/fixed_red_black_tree_nodes.hpp" #include "fixed_containers/max_size.hpp" #include "fixed_containers/memory.hpp" @@ -76,6 +78,59 @@ static_assert(std::bidirectional_iterator); } // namespace +// verify that the FixedMap takes the expected amount of memory +namespace +{ +using V = std::array, 30>; +constexpr std::size_t CAP = 130; + +template +using CompactPoolFixedMap = + FixedMap, + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, + FixedIndexBasedPoolStorage>; +static_assert(std::is_same_v, CompactPoolFixedMap>); + +template +using CompactContiguousFixedMap = + FixedMap, + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::EMBEDDED_COLOR, + FixedIndexBasedContiguousStorage>; + +template +using DedicatedColorBitPoolFixedMap = + FixedMap, + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::DEDICATED_COLOR, + FixedIndexBasedContiguousStorage>; + +template +using DedicatedColorBitContiguousFixedMap = + FixedMap, + fixed_red_black_tree_detail::RedBlackTreeNodeColorCompactness::DEDICATED_COLOR, + FixedIndexBasedContiguousStorage>; + +// The reference boost-based fixed_map (with an array-backed pool-allocator) was at 51000 +// at the time of writing. +static_assert(consteval_compare::equal<50992, sizeof(FixedMap)>); +static_assert(consteval_compare::equal<50992, sizeof(CompactPoolFixedMap)>); +static_assert(consteval_compare::equal<50992, sizeof(CompactContiguousFixedMap)>); +static_assert(consteval_compare::equal<52032, sizeof(DedicatedColorBitPoolFixedMap)>); +static_assert( + consteval_compare::equal<52032, sizeof(DedicatedColorBitContiguousFixedMap)>); +} // namespace + TEST(FixedMap, DefaultConstructor) { constexpr FixedMap VAL1{}; From ad329c3fc0b87222f218f4380cbb8c1618d83c9a Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Sat, 13 Jul 2024 14:22:24 -0700 Subject: [PATCH 2/4] [map_copy] Greatly reduce the number of steps in the test --- test/benchmarks/map_copy.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/test/benchmarks/map_copy.cpp b/test/benchmarks/map_copy.cpp index dd2ea125..4685ceac 100644 --- a/test/benchmarks/map_copy.cpp +++ b/test/benchmarks/map_copy.cpp @@ -95,21 +95,24 @@ void benchmark_map_iterate_copy_shuffled(benchmark::State& state) benchmark::DoNotOptimize(instance2); } } + +constexpr std::size_t MAXIMUM_SIZE_LIMIT = 8 << 14; +constexpr std::size_t START = 512; } // namespace -BENCHMARK( - benchmark_map_copy_fresh>) - ->DenseRange(1024, 8 << 14, 1024); +BENCHMARK(benchmark_map_copy_fresh< + FixedUnorderedMap>) + ->Range(START, MAXIMUM_SIZE_LIMIT); BENCHMARK(benchmark_map_iterate_copy_fresh< - FixedUnorderedMap>) - ->DenseRange(1024, 8 << 14, 1024); + FixedUnorderedMap>) + ->Range(START, MAXIMUM_SIZE_LIMIT); -BENCHMARK( - benchmark_map_copy_shuffled>) - ->DenseRange(1024, 8 << 14, 1024); +BENCHMARK(benchmark_map_copy_shuffled< + FixedUnorderedMap>) + ->Range(START, MAXIMUM_SIZE_LIMIT); BENCHMARK(benchmark_map_iterate_copy_shuffled< - FixedUnorderedMap>) - ->DenseRange(1024, 8 << 14, 1024); + FixedUnorderedMap>) + ->Range(START, MAXIMUM_SIZE_LIMIT); } // namespace fixed_containers From 4c4a8db7f747a6ec5996f8c1140f8b66931b226e Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Sat, 13 Jul 2024 14:36:26 -0700 Subject: [PATCH 3/4] [benchmark] Extract variables --- test/benchmarks/map_clear.cpp | 48 ++++++++++++++++++++-------------- test/benchmarks/map_lookup.cpp | 41 +++++++++++++++++++---------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/test/benchmarks/map_clear.cpp b/test/benchmarks/map_clear.cpp index cf5ac15e..696a2497 100644 --- a/test/benchmarks/map_clear.cpp +++ b/test/benchmarks/map_clear.cpp @@ -82,31 +82,39 @@ void benchmark_array_clear(benchmark::State& state) benchmark::DoNotOptimize(instance); } } -} // namespace - -constexpr std::size_t MAX_SIZE = 8 << 13; - -BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_clear>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_reconstruct>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_clear>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_reconstruct>)->Range(16, MAX_SIZE); - -BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_clear>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_reconstruct>)->Range(16, MAX_SIZE); +constexpr std::size_t MAXIMUM_SIZE_LIMIT = 8 << 13; +constexpr std::size_t START = 16; +} // namespace -BENCHMARK(benchmark_map_copy>)->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_clear>) - ->Range(16, MAX_SIZE); -BENCHMARK(benchmark_map_copy_then_reconstruct>) - ->Range(16, MAX_SIZE); +BENCHMARK(benchmark_map_copy>)->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_clear>)->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_reconstruct>) + ->Range(START, MAXIMUM_SIZE_LIMIT); + +BENCHMARK(benchmark_map_copy>)->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_clear>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_reconstruct>) + ->Range(START, MAXIMUM_SIZE_LIMIT); + +BENCHMARK(benchmark_map_copy>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_clear>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_reconstruct>) + ->Range(START, MAXIMUM_SIZE_LIMIT); + +BENCHMARK(benchmark_map_copy>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_clear>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_copy_then_reconstruct>) + ->Range(START, MAXIMUM_SIZE_LIMIT); // more-or-less the theoretical best performance we could possibly get for a full FixedUnorderedMap // (just 0 out every bucket) -BENCHMARK(benchmark_array_clear>); +BENCHMARK(benchmark_array_clear>); } // namespace fixed_containers diff --git a/test/benchmarks/map_lookup.cpp b/test/benchmarks/map_lookup.cpp index 4e10be8e..2f15dbf9 100644 --- a/test/benchmarks/map_lookup.cpp +++ b/test/benchmarks/map_lookup.cpp @@ -94,22 +94,35 @@ void benchmark_map_iterate_shuffled(benchmark::State& state) } } } + +constexpr std::size_t MAXIMUM_SIZE_LIMIT = 8 << 14; +constexpr std::size_t START = 256; + } // namespace -BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_lookup_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_lookup_shuffled>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_lookup_shuffled>)->Range(256, 8 << 14); - -BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_iterate_fresh>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_iterate_shuffled>)->Range(256, 8 << 14); -BENCHMARK(benchmark_map_iterate_shuffled>) - ->Range(256, 8 << 14); +BENCHMARK(benchmark_map_lookup_fresh>)->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_lookup_fresh>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_lookup_fresh>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_lookup_fresh>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_lookup_shuffled>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_lookup_shuffled>) + ->Range(START, MAXIMUM_SIZE_LIMIT); + +BENCHMARK(benchmark_map_iterate_fresh>)->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_iterate_fresh>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_iterate_fresh>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_iterate_fresh>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_iterate_shuffled>) + ->Range(START, MAXIMUM_SIZE_LIMIT); +BENCHMARK(benchmark_map_iterate_shuffled>) + ->Range(START, MAXIMUM_SIZE_LIMIT); } // namespace fixed_containers From 887bc90a921b30838410ec6ab7a581d711b44b12 Mon Sep 17 00:00:00 2001 From: Alexander Karatarakis Date: Sat, 13 Jul 2024 14:55:40 -0700 Subject: [PATCH 4/4] [benchmarks] Do everything on the heap to avoid stack overflows --- test/benchmarks/map_clear.cpp | 28 ++++++++++++++++++++-------- test/benchmarks/map_copy.cpp | 34 +++++++++++++++++++++++++--------- test/benchmarks/map_lookup.cpp | 16 ++++++++++++---- test/benchmarks/map_utils.hpp | 12 +++++------- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/test/benchmarks/map_clear.cpp b/test/benchmarks/map_clear.cpp index 696a2497..28cb884f 100644 --- a/test/benchmarks/map_clear.cpp +++ b/test/benchmarks/map_clear.cpp @@ -1,5 +1,6 @@ #include "fixed_containers/fixed_map.hpp" #include "fixed_containers/fixed_unordered_map.hpp" +#include "fixed_containers/memory.hpp" #include @@ -7,6 +8,7 @@ #include #include #include +#include #include namespace fixed_containers @@ -18,7 +20,8 @@ template void benchmark_map_copy(benchmark::State& state) { const int64_t nelem = state.range(0); - MapType instance = {}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); using KeyType = typename MapType::key_type; for (int64_t i = 0; i < nelem; i++) @@ -26,9 +29,11 @@ void benchmark_map_copy(benchmark::State& state) instance.try_emplace(static_cast(i)); } + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{instance}; + memory::destroy_and_construct_at_address_of(instance2, instance); benchmark::DoNotOptimize(instance2); } } @@ -37,16 +42,19 @@ template void benchmark_map_copy_then_clear(benchmark::State& state) { using KeyType = typename MapType::key_type; - MapType instance{}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); const int64_t nelem = state.range(0); for (int64_t i = 0; i < nelem; i++) { instance.try_emplace(static_cast(i)); } + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{instance}; + memory::destroy_and_construct_at_address_of(instance2, instance); instance2.clear(); benchmark::DoNotOptimize(instance2); } @@ -56,17 +64,20 @@ template void benchmark_map_copy_then_reconstruct(benchmark::State& state) { using KeyType = typename MapType::key_type; - MapType instance{}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); const int64_t nelem = state.range(0); for (int64_t i = 0; i < nelem; i++) { instance.try_emplace(static_cast(i)); } + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{instance}; - instance2 = {}; + memory::destroy_and_construct_at_address_of(instance2, instance); + memory::destroy_and_construct_at_address_of(instance2); benchmark::DoNotOptimize(instance2); } } @@ -74,7 +85,8 @@ void benchmark_map_copy_then_reconstruct(benchmark::State& state) template void benchmark_array_clear(benchmark::State& state) { - ArrType instance{}; + const std::unique_ptr instance_ptr = std::make_unique(); + ArrType& instance = *instance_ptr.get(); for (auto _ : state) { diff --git a/test/benchmarks/map_copy.cpp b/test/benchmarks/map_copy.cpp index 4685ceac..2761693e 100644 --- a/test/benchmarks/map_copy.cpp +++ b/test/benchmarks/map_copy.cpp @@ -2,10 +2,13 @@ #include "../mock_testing_types.hpp" #include "fixed_containers/fixed_unordered_map.hpp" +#include "fixed_containers/memory.hpp" #include +#include #include +#include namespace fixed_containers { @@ -16,7 +19,8 @@ template void benchmark_map_copy_fresh(benchmark::State& state) { const int64_t nelem = state.range(0); - MapType instance = {}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); using KeyType = typename MapType::key_type; for (int64_t i = 0; i < nelem; i++) @@ -24,9 +28,11 @@ void benchmark_map_copy_fresh(benchmark::State& state) instance.try_emplace(static_cast(i)); } + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{instance}; + memory::destroy_and_construct_at_address_of(instance2, instance); benchmark::DoNotOptimize(instance2); } } @@ -35,17 +41,19 @@ template void benchmark_map_iterate_copy_fresh(benchmark::State& state) { const int64_t nelem = state.range(0); - MapType instance = {}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); using KeyType = typename MapType::key_type; for (int64_t i = 0; i < nelem; i++) { instance.try_emplace(static_cast(i)); } - + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{}; + memory::destroy_and_construct_at_address_of(instance2); for (auto elem : instance) { instance2.try_emplace(elem.first, elem.second); @@ -58,7 +66,9 @@ template void benchmark_map_copy_shuffled(benchmark::State& state) { const int64_t nelem = state.range(0); - auto instance = map_benchmarks::make_shuffled_map(); + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); + map_benchmarks::make_shuffled_map(instance); using KeyType = typename MapType::key_type; for (int64_t i = 0; i < nelem; i++) @@ -66,9 +76,11 @@ void benchmark_map_copy_shuffled(benchmark::State& state) instance.try_emplace(static_cast(i)); } + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{instance}; + memory::destroy_and_construct_at_address_of(instance2, instance); benchmark::DoNotOptimize(instance2); } } @@ -77,7 +89,9 @@ template void benchmark_map_iterate_copy_shuffled(benchmark::State& state) { const int64_t nelem = state.range(0); - auto instance = map_benchmarks::make_shuffled_map(); + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); + map_benchmarks::make_shuffled_map(instance); using KeyType = typename MapType::key_type; for (int64_t i = 0; i < nelem; i++) @@ -85,9 +99,11 @@ void benchmark_map_iterate_copy_shuffled(benchmark::State& state) instance.try_emplace(static_cast(i)); } + const std::unique_ptr instance_ptr2 = std::make_unique(); + MapType& instance2 = *instance_ptr2.get(); for (auto _ : state) { - MapType instance2{}; + memory::destroy_and_construct_at_address_of(instance2); for (auto elem : instance) { instance2.try_emplace(elem.first, elem.second); diff --git a/test/benchmarks/map_lookup.cpp b/test/benchmarks/map_lookup.cpp index 2f15dbf9..38ee67c6 100644 --- a/test/benchmarks/map_lookup.cpp +++ b/test/benchmarks/map_lookup.cpp @@ -5,8 +5,10 @@ #include +#include #include #include +#include #include namespace fixed_containers @@ -17,7 +19,8 @@ template void benchmark_map_lookup_fresh(benchmark::State& state) { using KeyType = typename MapType::key_type; - MapType instance{}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); const int64_t nelem = state.range(0); for (int64_t i = 0; i < nelem; i++) { @@ -38,7 +41,9 @@ template void benchmark_map_lookup_shuffled(benchmark::State& state) { using KeyType = typename MapType::key_type; - auto instance = map_benchmarks::make_shuffled_map(); + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); + map_benchmarks::make_shuffled_map(instance); const int64_t nelem = state.range(0); for (int64_t i = 0; i < nelem; i++) { @@ -59,7 +64,8 @@ template void benchmark_map_iterate_fresh(benchmark::State& state) { using KeyType = typename MapType::key_type; - MapType instance{}; + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); const int64_t nelem = state.range(0); for (int64_t i = 0; i < nelem; i++) { @@ -79,7 +85,9 @@ template void benchmark_map_iterate_shuffled(benchmark::State& state) { using KeyType = typename MapType::key_type; - auto instance = map_benchmarks::make_shuffled_map(); + const std::unique_ptr instance_ptr = std::make_unique(); + MapType& instance = *instance_ptr.get(); + map_benchmarks::make_shuffled_map(instance); const int64_t nelem = state.range(0); for (int64_t i = 0; i < nelem; i++) { diff --git a/test/benchmarks/map_utils.hpp b/test/benchmarks/map_utils.hpp index 52df78b0..a939599f 100644 --- a/test/benchmarks/map_utils.hpp +++ b/test/benchmarks/map_utils.hpp @@ -7,7 +7,7 @@ namespace fixed_containers::map_benchmarks { template -[[maybe_unused]] static void del(MapType& map, int64_t divisor) +constexpr void del(MapType& map, int64_t divisor) { auto iter = map.begin(); while (iter != map.end()) @@ -24,7 +24,7 @@ template } template -[[maybe_unused]] static void replace_low(MapType& map, std::size_t divisor) +constexpr void replace_low(MapType& map, std::size_t divisor) { using KeyType = typename MapType::key_type; for (std::size_t i = 0; i < map.max_size(); i += divisor) @@ -34,7 +34,7 @@ template } template -[[maybe_unused]] static void replace_high(MapType& map, std::size_t divisor) +constexpr void replace_high(MapType& map, std::size_t divisor) { using KeyType = typename MapType::key_type; // find the largest multiple smaller than `n` @@ -49,10 +49,10 @@ template // create a "well-used" map, so that new elements will be inserted into dispersed spots in the map // instead of spots with good memory locality template -[[maybe_unused]] static MapType make_shuffled_map() +constexpr void make_shuffled_map(MapType& instance) { using KeyType = typename MapType::key_type; - MapType instance{}; + instance.clear(); // fill the map completely for (std::size_t i = 0; i < instance.max_size(); i++) { @@ -83,8 +83,6 @@ template del(instance, 1023); del(instance, 15); del(instance, 1); - - return instance; } } // namespace fixed_containers::map_benchmarks