Merge commit 'f1087e81ecdca5a59ba5ffca684c955c5b38f7c2' as 'third_party/unordered_dense'

This commit is contained in:
Siarhei Fedartsou
2024-05-30 19:06:16 +02:00
2383 changed files with 16243 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
#include <ankerl/unordered_dense.h> // for map, operator==
#include <third-party/nanobench.h> // for Rng, Bench
#include <app/doctest.h> // for TestCase, skip, TEST_CASE, test_...
#include <fmt/core.h> // for format
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <string_view> // for string_view
#include <unordered_map> // for unordered_map, operator==
template <typename Map>
void bench(std::string_view name) {
auto a = Map();
auto rng = ankerl::nanobench::Rng(123);
for (size_t i = 0; i < 1000000; ++i) {
a.try_emplace(rng(), rng());
}
Map b;
ankerl::nanobench::Bench().batch(a.size() * 2).run(fmt::format("copy {}", name), [&] {
b = a;
a = b;
});
REQUIRE(a == b);
}
#if 0
TEST_CASE("bench_copy_rhn" * doctest::test_suite("bench") * doctest::skip()) {
bench<robin_hood::unordered_node_map<uint64_t, uint64_t>>("robin_hood::unordered_node_map");
}
TEST_CASE("bench_copy_rhf" * doctest::test_suite("bench") * doctest::skip()) {
bench<robin_hood::unordered_flat_map<uint64_t, uint64_t>>("robin_hood::unordered_flat_map");
}
#endif
TEST_CASE_MAP("bench_copy_udm" * doctest::test_suite("bench") * doctest::skip(), uint64_t, uint64_t) {
bench<map_t>("ankerl::unordered_dense::map");
}
TEST_CASE("bench_copy_std" * doctest::test_suite("bench") * doctest::skip()) {
bench<std::unordered_map<uint64_t, uint64_t>>("std::unordered_map");
}
+101
View File
@@ -0,0 +1,101 @@
#include <ankerl/unordered_dense.h> // for map
#include <app/name_of_type.h> // for name_of_type
#include <third-party/nanobench.h> // for Rng
#include <app/doctest.h> // for TestCase, skip, ResultBuilder
#include <fmt/core.h> // for format, print
#include <algorithm> // for fill_n
#include <array> // for array
#include <chrono> // for duration, operator-, steady_clock
#include <cstddef> // for size_t
#include <unordered_map> // for unordered_map, operator!=
#include <vector> // for vector
template <typename Map>
void bench() {
static constexpr size_t num_total = 4;
auto required_checksum = std::array<size_t, 5>{200000, 25198620, 50197240, 75195862, 100194482};
auto total = std::chrono::steady_clock::duration();
for (size_t num_found = 0; num_found < 5; ++num_found) {
auto title = fmt::format("random find {}% success {}", num_found * 100 / num_total, name_of_type<Map>());
auto rng = ankerl::nanobench::Rng(123);
size_t checksum = 0;
using ary_t = std::array<bool, num_total>;
auto insert_random = ary_t();
insert_random.fill(true);
for (typename ary_t::size_type i = 0; i < num_found; ++i) {
insert_random[i] = false;
}
auto another_unrelated_rng = ankerl::nanobench::Rng(987654321);
auto const another_unrelated_rng_initial_state = another_unrelated_rng.state();
auto find_rng = ankerl::nanobench::Rng(another_unrelated_rng_initial_state);
{
static constexpr size_t num_inserts = 200000;
static constexpr size_t num_finds_per_insert = 500;
static constexpr size_t num_finds_per_iter = num_finds_per_insert * num_total;
Map map;
size_t i = 0;
size_t find_count = 0;
auto before = std::chrono::steady_clock::now();
do {
// insert numTotal entries: some random, some sequential.
rng.shuffle(insert_random);
for (bool const is_random_to_insert : insert_random) {
auto val = another_unrelated_rng();
if (is_random_to_insert) {
map[static_cast<size_t>(rng())] = static_cast<size_t>(1);
} else {
map[static_cast<size_t>(val)] = static_cast<size_t>(1);
}
++i;
}
// the actual benchmark code which should be as fast as possible
for (size_t j = 0; j < num_finds_per_iter; ++j) {
if (++find_count > i) {
find_count = 0;
find_rng = ankerl::nanobench::Rng(another_unrelated_rng_initial_state);
}
auto it = map.find(static_cast<size_t>(find_rng()));
if (it != map.end()) {
checksum += it->second;
}
}
} while (i < num_inserts);
checksum += map.size();
auto after = std::chrono::steady_clock::now();
total += after - before;
fmt::print("{}s {}\n", std::chrono::duration<double>(after - before).count(), title);
}
REQUIRE(checksum == required_checksum[num_found]);
}
fmt::print("{}s total\n", std::chrono::duration<double>(total).count());
}
// 26.81
TEST_CASE("bench_find_random_uo" * doctest::test_suite("bench") * doctest::skip()) {
bench<std::unordered_map<size_t, size_t>>();
}
#if 0
// 10.55
TEST_CASE("bench_find_random_rh" * doctest::test_suite("bench") * doctest::skip()) {
bench<robin_hood::unordered_flat_map<size_t, size_t>>();
}
#endif
// 8.87
TEST_CASE_MAP("bench_find_random_udm" * doctest::test_suite("bench") * doctest::skip(), size_t, size_t) {
bench<map_t>();
}
+174
View File
@@ -0,0 +1,174 @@
#include <ankerl/unordered_dense.h> // for map
#include <app/counting_allocator.h>
#include <app/doctest.h> // for TestCase, skip, ResultBuilder
#include <fmt/core.h> // for format, print
#include <unordered_map>
#include <vector>
#if __has_include("boost/unordered/unordered_flat_map.hpp")
# if defined(__clang__)
# pragma clang diagnostic ignored "-Wold-style-cast"
# endif
# include "boost/unordered/unordered_flat_map.hpp"
# define HAS_BOOST_UNORDERED_FLAT_MAP() 1 // NOLINT(cppcoreguidelines-macro-usage)
#else
# define HAS_BOOST_UNORDERED_FLAT_MAP() 0 // NOLINT(cppcoreguidelines-macro-usage)
#endif
#if 0 && __has_include("absl/container/flat_hash_map.h")
# if defined(__clang__)
# pragma clang diagnostic ignored "-Wdeprecated-builtins"
# pragma clang diagnostic ignored "-Wsign-conversion"
# endif
# include <absl/container/flat_hash_map.h>
# define HAS_ABSL() 1 // NOLINT(cppcoreguidelines-macro-usage)
#else
# define HAS_ABSL() 0 // NOLINT(cppcoreguidelines-macro-usage)
#endif
class vec2 {
uint32_t m_xy;
public:
constexpr vec2(uint16_t x, uint16_t y)
: m_xy{static_cast<uint32_t>(x) << 16U | y} {}
constexpr explicit vec2(uint32_t xy)
: m_xy(xy) {}
[[nodiscard]] constexpr auto pack() const -> uint32_t {
return m_xy;
};
[[nodiscard]] constexpr auto add_x(uint16_t x) const -> vec2 {
return vec2{m_xy + (static_cast<uint32_t>(x) << 16U)};
}
[[nodiscard]] constexpr auto add_y(uint16_t y) const -> vec2 {
return vec2{(m_xy & 0xffff0000) | ((m_xy + y) & 0xffff)};
}
template <typename Op>
constexpr void for_each_surrounding(Op&& op) const {
uint32_t v = m_xy;
uint32_t upper = (v & 0xffff0000U) - 0x10000;
uint32_t l1 = (v - 1) & 0xffffU;
uint32_t l2 = v & 0xffffU;
uint32_t l3 = (v + 1) & 0xffffU;
op(upper | l1);
op(upper | l2);
op(upper | l3);
upper += 0x10000;
op(upper | l1);
// op(upper | l2);
op(upper | l3);
upper += 0x10000;
op(upper | l1);
op(upper | l2);
op(upper | l3);
}
};
template <typename Map>
auto game_of_life(std::string_view name, size_t nsteps, Map map1, std::vector<vec2> state) -> size_t {
auto before = std::chrono::steady_clock::now();
map1.clear();
auto map2 = map1; // copy the empty map so we get the allocator
for (auto& v : state) {
v = v.add_x(UINT16_MAX / 2).add_y(UINT16_MAX / 2);
map1[v.pack()] = true;
v.for_each_surrounding([&](uint32_t xy) {
map1.emplace(xy, false);
});
}
auto* m1 = &map1;
auto* m2 = &map2;
for (size_t i = 0; i < nsteps; ++i) {
for (auto const& kv : *m1) {
auto const& pos = kv.first;
auto alive = kv.second;
int neighbors = 0;
vec2{pos}.for_each_surrounding([&](uint32_t xy) {
if (auto x = m1->find(xy); x != m1->end()) {
neighbors += x->second;
}
});
if ((alive && (neighbors == 2 || neighbors == 3)) || (!alive && neighbors == 3)) {
(*m2)[pos] = true;
vec2{pos}.for_each_surrounding([&](uint32_t xy) {
m2->emplace(xy, false);
});
}
}
m1->clear();
std::swap(m1, m2);
}
size_t final_population = 0;
for (auto const& kv : *m1) {
final_population += kv.second;
}
auto after = std::chrono::steady_clock::now();
fmt::print("{}s {}\n", std::chrono::duration<double>(after - before).count(), name);
return final_population;
}
TEST_CASE("gameoflife_gotts-dots" * doctest::test_suite("bench") * doctest::skip()) {
// https://conwaylife.com/wiki/Gotts_dots
auto state = std::vector<vec2>{
{0, 0}, {0, 1}, {0, 2}, // 1
{4, 11}, {5, 12}, {6, 13}, {7, 12}, {8, 11}, // 2
{9, 13}, {9, 14}, {9, 15}, // 3
{185, 24}, {186, 25}, {186, 26}, {186, 27}, {185, 27}, {184, 27}, {183, 27}, {182, 26}, // 4
{179, 28}, {180, 29}, {181, 29}, {179, 30}, // 5
{182, 32}, {183, 31}, {184, 31}, {185, 31}, {186, 31}, {186, 32}, {186, 33}, {185, 34}, // 6
{175, 35}, {176, 36}, {170, 37}, {176, 37}, {171, 38}, {172, 38}, {173, 38}, {174, 38}, {175, 38}, {176, 38}, // 7
};
// size_t nsteps = 200;
// size_t nsteps = 2000;
size_t nsteps = 4000;
// size_t nsteps = 10000;
auto pop = size_t();
{
using map_t = ankerl::unordered_dense::map<uint32_t, bool>;
pop = game_of_life("ankerl::unordered_dense::map", nsteps, map_t(), state);
}
{
using map_t = ankerl::unordered_dense::segmented_map<uint32_t, bool>;
auto new_pop = game_of_life("ankerl::unordered_dense::segmented_map", nsteps, map_t(), state);
REQUIRE(pop == new_pop);
}
#if HAS_BOOST_UNORDERED_FLAT_MAP
{
using map_t = boost::unordered_flat_map<uint32_t, bool>;
auto new_pop = game_of_life("boost::unordered_flat_map", nsteps, map_t(), state);
REQUIRE(pop == new_pop);
}
#endif
#if HAS_ABSL()
{
using map_t = absl::flat_hash_map<uint32_t, bool>;
auto new_pop = game_of_life("absl::flat_hash_map", nsteps, map_t(), state);
REQUIRE(pop == new_pop);
}
#endif
{
using map_t = std::unordered_map<uint32_t, bool>;
auto new_pop = game_of_life("std::unordered_map", nsteps, map_t(), state);
REQUIRE(pop == new_pop);
}
}
@@ -0,0 +1,344 @@
#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);
}
@@ -0,0 +1,114 @@
// #include "absl/container/flat_hash_map.h"
#include <ankerl/unordered_dense.h> // for map, operator==
#include <app/counting_allocator.h>
#include <third-party/nanobench.h>
#if __has_include("boost/unordered/unordered_flat_map.hpp")
# if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wold-style-cast"
# endif
# include "boost/unordered/unordered_flat_map.hpp"
# define HAS_BOOST_UNORDERED_FLAT_MAP() 1 // NOLINT(cppcoreguidelines-macro-usage)
#else
# define HAS_BOOST_UNORDERED_FLAT_MAP() 0 // NOLINT(cppcoreguidelines-macro-usage)
#endif
#include <doctest.h>
#include <fmt/ostream.h>
#include <deque>
#include <filesystem>
#include <fstream>
#include <unordered_map>
template <typename Map>
void evaluate_map(Map& map) {
auto rng = ankerl::nanobench::Rng{1234};
auto num_elements = size_t{10'000'000};
for (uint64_t i = 0; i < num_elements; ++i) {
map[rng()] = i;
}
REQUIRE(map.size() == num_elements);
}
using hash_t = ankerl::unordered_dense::hash<uint64_t>;
using eq_t = std::equal_to<uint64_t>;
using pair_t = std::pair<uint64_t, uint64_t>;
using pair_const_t = std::pair<const uint64_t, uint64_t>;
using alloc_t = counting_allocator<pair_t>;
using alloc_const_t = counting_allocator<pair_const_t>;
TEST_CASE("allocated_memory_std_vector" * doctest::skip()) {
auto counters = counts_for_allocator{};
{
using vec_t = std::vector<pair_t, alloc_t>;
using map_t = ankerl::unordered_dense::map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
auto map = map_t(0, hash_t{}, eq_t{}, alloc_t{&counters});
evaluate_map(map);
}
counters.save("allocated_memory_std_vector.txt");
}
#if HAS_BOOST_UNORDERED_FLAT_MAP()
TEST_CASE("allocated_memory_boost_flat_map" * doctest::skip()) {
auto counters = counts_for_allocator{};
{
using map_t = boost::unordered_flat_map<uint64_t, uint64_t, hash_t, eq_t, alloc_t>;
auto map = map_t(alloc_t{&counters});
evaluate_map(map);
}
counters.save("allocated_memory_unordered_flat_map.txt");
}
#endif
TEST_CASE("allocated_memory_std_deque" * doctest::skip()) {
auto counters = counts_for_allocator{};
{
using vec_t = std::deque<pair_t, alloc_t>;
using map_t = ankerl::unordered_dense::map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
auto map = map_t(0, hash_t{}, eq_t{}, alloc_t{&counters});
evaluate_map(map);
}
counters.save("allocated_memory_std_deque.txt");
}
TEST_CASE("allocated_memory_segmented_vector" * doctest::skip()) {
auto counters = counts_for_allocator{};
{
using vec_t = ankerl::unordered_dense::segmented_vector<pair_t, alloc_t>;
using map_t = ankerl::unordered_dense::segmented_map<uint64_t, uint64_t, hash_t, eq_t, vec_t>;
auto map = map_t{0, hash_t{}, eq_t{}, alloc_t{&counters}};
evaluate_map(map);
}
counters.save("allocated_memory_segmented_vector.txt");
}
#if 0
TEST_CASE("allocated_memory_std_unordered_map" * doctest::skip()) {
auto counters = counts_for_allocator{};
{
using map_t = std::unordered_map<uint64_t, uint64_t, hash_t, eq_t, alloc_const_t>;
auto map = map_t(0, alloc_t{&counters});
evaluate_map(map);
}
counters.save("allocated_memory_std_unordered_map.txt");
}
TEST_CASE("allocated_memory_boost_unordered_flat_map" * doctest::skip()) {
auto counters = counts_for_allocator{};
{
using map_t = absl::
flat_hash_map<uint64_t, uint64_t, absl::container_internal::hash_default_hash<uint64_t>, eq_t, alloc_const_t>;
auto map = map_t(0, alloc_t{&counters});
evaluate_map(map);
}
counters.save("allocated_memory_absl_flat_hash_map.txt");
}
#endif
+53
View File
@@ -0,0 +1,53 @@
#include <ankerl/unordered_dense.h> // for map
#include <third-party/nanobench.h> // for Rng, doNotOptimizeAway, Bench
#include <doctest.h> // for TestCase, skip, TEST_CASE, test_...
#include <fmt/core.h> // for format
#include <cstddef> // for size_t
#include <cstdint> // for uint64_t
#include <string_view> // for string_view
#include <unordered_map> // for unordered_map, swap
#include <utility> // for swap
namespace {
template <typename Map>
void bench(std::string_view name) {
Map a;
Map b;
ankerl::nanobench::Rng rng(123);
ankerl::nanobench::Bench bench;
for (size_t j = 0; j < 10000; ++j) {
a[rng()];
b[rng()];
}
bench.run(fmt::format("swap {}", name), [&] {
std::swap(a, b);
});
ankerl::nanobench::doNotOptimizeAway(&a);
ankerl::nanobench::doNotOptimizeAway(&b);
}
} // namespace
#if 0
TEST_CASE("bench_swap_rhn" * doctest::test_suite("bench") * doctest::skip()) {
bench<robin_hood::unordered_node_map<uint64_t, uint64_t>>("robin_hood::unordered_node_map");
}
TEST_CASE("bench_swap_rhf" * doctest::test_suite("bench") * doctest::skip()) {
bench<robin_hood::unordered_flat_map<uint64_t, uint64_t>>("robin_hood::unordered_flat_map");
}
#endif
TEST_CASE("bench_swap_std" * doctest::test_suite("bench") * doctest::skip()) {
bench<std::unordered_map<uint64_t, uint64_t>>("std::unordered_map");
}
TEST_CASE("bench_swap_udm" * doctest::test_suite("bench") * doctest::skip()) {
bench<ankerl::unordered_dense::map<uint64_t, uint64_t>>("ankerl::unordered_dense::map");
}