diff --git a/BUILD.bazel b/BUILD.bazel index e308ef58..7fddec97 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1157,20 +1157,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"], @@ -1621,6 +1607,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 6cadad41..151e96ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,8 +171,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) @@ -231,6 +229,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..60e85bfe --- /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 +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{instance}; + benchmark::DoNotOptimize(instance2); + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{instance}; + instance2.clear(); + benchmark::DoNotOptimize(instance2); + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{instance}; + instance2 = {}; + benchmark::DoNotOptimize(instance2); + } +} + +template +[[maybe_unused]] void benchmark_array_clear(benchmark::State& state) +{ + ArrType instance{}; + + for (const auto& iter : 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..60e0317d --- /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 +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{instance}; + benchmark::DoNotOptimize(instance2); + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{}; + for (const auto& elem : instance) + { + instance2.try_emplace(elem.first, elem.second); + } + benchmark::DoNotOptimize(instance2); + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{instance}; + benchmark::DoNotOptimize(instance2); + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + MapType instance2{}; + for (const 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..5ef21e25 --- /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 +[[maybe_unused]] 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 (const auto& iter : state) + { + for (int64_t i = 0; i < nelem; i += nelem / 8) + { + auto& entry = instance.at(static_cast(i)); + benchmark::DoNotOptimize(entry); + } + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + for (int64_t i = 0; i < nelem; i += nelem / 8) + { + auto& entry = instance.at(static_cast(i)); + benchmark::DoNotOptimize(entry); + } + } +} + +template +[[maybe_unused]] 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 (const auto& iter : state) + { + for (auto pair : instance) + { + benchmark::DoNotOptimize(pair.second); + } + } +} + +template +[[maybe_unused]] 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 (const auto& iter : 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 5383f322..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 unused : state) - { - auto& entry = instance.at(7); - benchmark::DoNotOptimize(entry); - } -} - -BENCHMARK(benchmark_map_lookup>); -BENCHMARK(benchmark_map_lookup>); -} // namespace -} // namespace fixed_containers - -BENCHMARK_MAIN();