osrm-backend/test/bench/quick_overall_map.cpp
Siarhei Fedartsou f1087e81ec Squashed 'third_party/unordered_dense/' content from commit 231e48c94
git-subtree-dir: third_party/unordered_dense
git-subtree-split: 231e48c9426bd21c273669e5fdcd042c146975cf
2024-05-30 19:06:16 +02:00

345 lines
12 KiB
C++

#include <ankerl/unordered_dense.h> // for map, hash
#include <app/geomean.h> // for geomean
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
#include <doctest.h> // for TestCase, skip, ResultBuilder
#include <fmt/core.h> // for print, format
#include <chrono> // for duration, operator-, high_resolu...
#include <cstdint> // for uint64_t
#include <cstring> // for size_t, memcpy
#include <deque> // for deque
#include <string> // for string, basic_string, operator==
#include <string_view> // for string_view, literals
#include <unordered_map> // for unordered_map, operator!=
#include <vector> // for vector
using namespace std::literals;
namespace {
template <typename K>
inline auto init_key() -> K {
return {};
}
template <typename T>
inline void randomize_key(ankerl::nanobench::Rng* rng, int n, T* key) {
// we limit ourselves to 32bit n
auto limited = (((*rng)() >> 32U) * static_cast<uint64_t>(n)) >> 32U;
*key = static_cast<T>(limited);
}
template <>
[[nodiscard]] inline auto init_key<std::string>() -> std::string {
std::string str;
str.resize(200);
return str;
}
inline void randomize_key(ankerl::nanobench::Rng* rng, int n, std::string* key) {
uint64_t k{};
randomize_key(rng, n, &k);
std::memcpy(key->data(), &k, sizeof(k));
}
// Random insert & erase
template <typename Map>
void bench_random_insert_erase(ankerl::nanobench::Bench* bench, std::string_view name) {
bench->run(fmt::format("{} random insert erase", name), [&] {
ankerl::nanobench::Rng rng(123);
size_t verifier{};
Map map;
auto key = init_key<typename Map::key_type>();
for (int n = 1; n < 20000; ++n) {
for (int i = 0; i < 200; ++i) {
randomize_key(&rng, n, &key);
map[key];
randomize_key(&rng, n, &key);
verifier += map.erase(key);
}
}
CHECK(verifier == 1994641U);
CHECK(map.size() == 9987U);
});
}
// iterate
template <typename Map>
void bench_iterate(ankerl::nanobench::Bench* bench, std::string_view name) {
size_t const num_elements = 5000;
auto key = init_key<typename Map::key_type>();
// insert
bench->run(fmt::format("{} iterate while adding then removing", name), [&] {
ankerl::nanobench::Rng rng(555);
Map map;
size_t result = 0;
for (size_t n = 0; n < num_elements; ++n) {
randomize_key(&rng, 1000000, &key);
map[key] = n;
for (auto const& key_val : map) {
result += key_val.second;
}
}
rng = ankerl::nanobench::Rng(555);
do {
randomize_key(&rng, 1000000, &key);
map.erase(key);
for (auto const& key_val : map) {
result += key_val.second;
}
} while (!map.empty());
CHECK(result == 62282755409U);
});
}
// 111.903 222
// 112.023 123123
template <typename Map>
void bench_random_find(ankerl::nanobench::Bench* bench, std::string_view name) {
bench->run(fmt::format("{} 50% probability to find", name), [&] {
uint64_t const seed = 123123;
ankerl::nanobench::Rng numbers_insert_rng(seed);
size_t numbers_insert_rng_calls = 0;
ankerl::nanobench::Rng numbers_search_rng(seed);
size_t numbers_search_rng_calls = 0;
ankerl::nanobench::Rng insertion_rng(123);
size_t checksum = 0;
size_t found = 0;
size_t not_found = 0;
Map map;
auto key = init_key<typename Map::key_type>();
for (size_t i = 0; i < 100000; ++i) {
randomize_key(&numbers_insert_rng, 1000000, &key);
++numbers_insert_rng_calls;
if (insertion_rng() & 1U) {
map[key] = i;
}
// search 100 entries in the map
for (size_t search = 0; search < 100; ++search) {
randomize_key(&numbers_search_rng, 1000000, &key);
++numbers_search_rng_calls;
auto it = map.find(key);
if (it != map.end()) {
checksum += it->second;
++found;
} else {
++not_found;
}
if (numbers_insert_rng_calls == numbers_search_rng_calls) {
numbers_search_rng = ankerl::nanobench::Rng(seed);
numbers_search_rng_calls = 0;
}
}
}
ankerl::nanobench::doNotOptimizeAway(checksum);
ankerl::nanobench::doNotOptimizeAway(found);
ankerl::nanobench::doNotOptimizeAway(not_found);
});
}
template <typename Map>
void bench_all(ankerl::nanobench::Bench* bench, std::string_view name) {
bench->title("benchmarking");
bench->minEpochTime(100ms);
bench_iterate<Map>(bench, name);
bench_random_insert_erase<Map>(bench, name);
bench_random_find<Map>(bench, name);
}
[[nodiscard]] auto geomean1(ankerl::nanobench::Bench const& bench) -> double {
return geomean(bench.results(), [](ankerl::nanobench::Result const& result) {
return result.median(ankerl::nanobench::Result::Measure::elapsed);
});
}
} // namespace
#if 0
// A relatively quick benchmark that should get a relatively good single number of how good the map
// is. It calculates geometric mean of several benchmarks.
TEST_CASE("bench_quick_overall_rhf" * doctest::test_suite("bench") * doctest::skip()) {
ankerl::nanobench::Bench bench;
bench_all<robin_hood::unordered_flat_map<uint64_t, size_t>>(&bench, "robin_hood::unordered_flat_map<uint64_t, size_t>");
bench_all<robin_hood::unordered_flat_map<std::string, size_t>>(&bench,
"robin_hood::unordered_flat_map<std::string, size_t>");
fmt::print("{} bench_quick_overall_rhf\n", geomean1(bench));
# ifdef ROBIN_HOOD_COUNT_ENABLED
std::cout << robin_hood::counts() << std::endl;
# endif
}
TEST_CASE("bench_quick_overall_rhn" * doctest::test_suite("bench") * doctest::skip()) {
ankerl::nanobench::Bench bench;
bench_all<robin_hood::unordered_node_map<uint64_t, size_t>>(&bench, "robin_hood::unordered_node_map<uint64_t, size_t>");
bench_all<robin_hood::unordered_node_map<std::string, size_t>>(&bench,
"robin_hood::unordered_node_map<std::string, size_t>");
fmt::print("{} bench_quick_overall_rhn\n", geomean1(bench));
# ifdef ROBIN_HOOD_COUNT_ENABLED
std::cout << robin_hood::counts() << std::endl;
# endif
}
#endif
using hash_t = ankerl::unordered_dense::hash<uint64_t>;
using eq_t = std::equal_to<uint64_t>;
using pair_t = std::pair<uint64_t, size_t>;
using hash_str_t = ankerl::unordered_dense::hash<std::string>;
using eq_str_t = std::equal_to<std::string>;
using pair_str_t = std::pair<std::string, size_t>;
TEST_CASE("bench_quick_overall_std" * doctest::test_suite("bench") * doctest::skip()) {
ankerl::nanobench::Bench bench;
bench_all<std::unordered_map<uint64_t, size_t>>(&bench, "std::unordered_map<uint64_t, size_t>");
bench_all<std::unordered_map<std::string, size_t>>(&bench, "std::unordered_map<std::string, size_t>");
fmt::print("{} bench_quick_overall_map_std\n", geomean1(bench));
}
TEST_CASE("bench_quick_overall_udm" * doctest::test_suite("bench") * doctest::skip()) {
ankerl::nanobench::Bench bench;
// bench.minEpochTime(1s);
using map_t = ankerl::unordered_dense::map<uint64_t, size_t>;
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t>");
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t>;
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t>");
fmt::print("{} bench_quick_overall_map_udm\n", geomean1(bench));
}
TEST_CASE("bench_quick_overall_segmented_vector" * doctest::test_suite("bench") * doctest::skip()) {
ankerl::nanobench::Bench bench;
// bench.minEpochTime(1s);
using vec_t = ankerl::unordered_dense::segmented_vector<pair_t>;
using map_t = ankerl::unordered_dense::segmented_map<uint64_t, size_t, hash_t, eq_t, vec_t>;
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t> segmented_vector");
using vec_str_t = ankerl::unordered_dense::segmented_vector<pair_str_t>;
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, vec_str_t>;
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t> segmented_vector");
fmt::print("{} bench_quick_overall_segmented_vector\n", geomean1(bench));
}
TEST_CASE("bench_quick_overall_deque" * doctest::test_suite("bench") * doctest::skip()) {
ankerl::nanobench::Bench bench;
// bench.minEpochTime(1s);
using vec_t = std::deque<pair_t>;
using map_t = ankerl::unordered_dense::map<uint64_t, size_t, hash_t, eq_t, vec_t>;
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t> deque");
using vec_str_t = std::deque<pair_str_t>;
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, vec_str_t>;
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t> deque");
fmt::print("{} bench_quick_overall_deque\n", geomean1(bench));
}
TEST_CASE("bench_quick_overall_udm_bigbucket" * doctest::test_suite("bench") * doctest::skip()) {
using bucket_t = ankerl::unordered_dense::bucket_type::big;
ankerl::nanobench::Bench bench;
// bench.minEpochTime(1s);
using alloc_t = std::allocator<pair_t>;
using map_t = ankerl::unordered_dense::map<uint64_t, size_t, hash_t, eq_t, alloc_t, bucket_t>;
bench_all<map_t>(&bench, "ankerl::unordered_dense::map<uint64_t, size_t>");
using alloc_str_t = std::allocator<pair_str_t>;
using map_str_t = ankerl::unordered_dense::map<std::string, size_t, hash_str_t, eq_str_t, alloc_str_t, bucket_t>;
bench_all<map_str_t>(&bench, "ankerl::unordered_dense::map<std::string, size_t>");
fmt::print("{} bench_quick_overall_map_udm\n", geomean1(bench));
}
template <typename Map>
void test_big() {
Map map;
auto rng = ankerl::nanobench::Rng();
for (uint64_t n = 0; n < 20000000; ++n) {
map[rng()];
map[rng()];
map[rng()];
map[rng()];
}
fmt::print("{} map.size()\n", map.size());
}
#if 0
// 3346376 max RSS, 0:12.40
TEST_CASE("memory_map_huge_rhf" * doctest::test_suite("bench") * doctest::skip()) {
test_big<robin_hood::unordered_flat_map<uint64_t, size_t>>();
}
// 2616352 max RSS, 0:24.72
TEST_CASE("memory_map_huge_rhn" * doctest::test_suite("bench") * doctest::skip()) {
test_big<robin_hood::unordered_node_map<uint64_t, size_t>>();
}
#endif
// 3296524 max RSS, 0:50.76
TEST_CASE("memory_map_huge_uo" * doctest::test_suite("bench") * doctest::skip()) {
test_big<std::unordered_map<uint64_t, size_t>>();
}
// 3149724 max RSS, 0:10.58
TEST_CASE("memory_map_huge_udm" * doctest::test_suite("bench") * doctest::skip()) {
test_big<ankerl::unordered_dense::map<uint64_t, size_t>>();
}
template <typename Set>
void bench_consecutive_insert(char const* name) {
auto before = std::chrono::high_resolution_clock::now();
Set s{};
for (uint64_t x = 0; x < 100000000; ++x) {
s.insert(x);
}
auto after = std::chrono::high_resolution_clock::now();
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
}
template <typename Set>
void bench_random_insert(char const* name) {
ankerl::nanobench::Rng rng(23);
auto before = std::chrono::high_resolution_clock::now();
Set s{};
for (uint64_t x = 0; x < 100000000; ++x) {
s.insert(rng());
}
auto after = std::chrono::high_resolution_clock::now();
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
}
template <typename Set>
void bench_shifted_insert(char const* name) {
auto before = std::chrono::high_resolution_clock::now();
Set s{};
for (uint64_t x = 0; x < 100000000; ++x) {
s.insert(x << 4U);
}
auto after = std::chrono::high_resolution_clock::now();
fmt::print("\t{}s, size={} for {}\n", std::chrono::duration<double>(after - before).count(), s.size(), name);
}