Merge commit 'f1087e81ecdca5a59ba5ffca684c955c5b38f7c2' as 'third_party/unordered_dense'
This commit is contained in:
+47
@@ -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");
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user