232 lines
6.9 KiB
C++
232 lines
6.9 KiB
C++
#include <ankerl/unordered_dense.h>
|
|
|
|
#include <app/doctest.h>
|
|
|
|
#include <fmt/core.h>
|
|
|
|
#include <cstddef> // for size_t
|
|
#include <cstdint> // for uint64_t
|
|
#include <stdexcept> // thrown in no_null_memory_resource
|
|
#include <string_view> // for string_view
|
|
#include <utility> // for move
|
|
#include <vector> // for vector
|
|
|
|
#if defined(ANKERL_UNORDERED_DENSE_PMR)
|
|
|
|
// windows' vector has different allocation behavior, macos has linker errors
|
|
# if __linux__
|
|
|
|
class logging_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
|
|
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
|
|
fmt::print("+ {} bytes, {} alignment\n", bytes, alignment);
|
|
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
|
|
}
|
|
|
|
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
|
|
fmt::print("- {} bytes, {} alignment, {} ptr\n", bytes, alignment, p);
|
|
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
|
|
}
|
|
|
|
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
|
|
return this == &other;
|
|
}
|
|
};
|
|
|
|
class no_null_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
|
|
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
|
|
if (bytes == 0) {
|
|
throw std::runtime_error("no_null_memory_resource::do_allocate should not do_allocate 0");
|
|
}
|
|
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
|
|
}
|
|
|
|
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
|
|
if (nullptr == p || 0U == bytes || 0U == alignment) {
|
|
throw std::runtime_error("no_null_memory_resource::do_deallocate should not deallocate with any 0 value");
|
|
}
|
|
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
|
|
}
|
|
|
|
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
|
|
return this == &other;
|
|
}
|
|
};
|
|
|
|
class track_peak_memory_resource : public ANKERL_UNORDERED_DENSE_PMR::memory_resource {
|
|
uint64_t m_peak = 0;
|
|
uint64_t m_current = 0;
|
|
uint64_t m_num_allocs = 0;
|
|
uint64_t m_num_deallocs = 0;
|
|
mutable uint64_t m_num_is_equals = 0;
|
|
|
|
auto do_allocate(std::size_t bytes, std::size_t alignment) -> void* override {
|
|
++m_num_allocs;
|
|
m_current += bytes;
|
|
if (m_current > m_peak) {
|
|
m_peak = m_current;
|
|
}
|
|
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->allocate(bytes, alignment);
|
|
}
|
|
|
|
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override {
|
|
if (p == nullptr) {
|
|
return;
|
|
}
|
|
++m_num_deallocs;
|
|
m_current -= bytes;
|
|
return ANKERL_UNORDERED_DENSE_PMR::new_delete_resource()->deallocate(p, bytes, alignment);
|
|
}
|
|
|
|
[[nodiscard]] auto do_is_equal(const ANKERL_UNORDERED_DENSE_PMR::memory_resource& other) const noexcept -> bool override {
|
|
++m_num_is_equals;
|
|
return this == &other;
|
|
}
|
|
|
|
public:
|
|
[[nodiscard]] auto current() const -> uint64_t {
|
|
return m_current;
|
|
}
|
|
|
|
[[nodiscard]] auto peak() const -> uint64_t {
|
|
return m_peak;
|
|
}
|
|
|
|
[[nodiscard]] auto num_allocs() const -> uint64_t {
|
|
return m_num_allocs;
|
|
}
|
|
|
|
[[nodiscard]] auto num_deallocs() const -> uint64_t {
|
|
return m_num_deallocs;
|
|
}
|
|
|
|
[[nodiscard]] auto num_is_equals() const -> uint64_t {
|
|
return m_num_is_equals;
|
|
}
|
|
};
|
|
|
|
TYPE_TO_STRING_PMR_MAP(uint64_t, uint64_t);
|
|
|
|
TEST_CASE_PMR_MAP("pmr", uint64_t, uint64_t) {
|
|
auto mr = track_peak_memory_resource();
|
|
{
|
|
REQUIRE(mr.current() == 0);
|
|
auto map = map_t(&mr);
|
|
|
|
for (size_t i = 0; i < 1; ++i) {
|
|
map[i] = i;
|
|
}
|
|
REQUIRE(mr.current() != 0);
|
|
|
|
// gets a copy, but it has the same memory resource
|
|
auto alloc = map.get_allocator();
|
|
REQUIRE(alloc.resource() == &mr);
|
|
}
|
|
REQUIRE(mr.current() == 0);
|
|
}
|
|
|
|
TEST_CASE_PMR_MAP("pmr_no_null", uint64_t, uint64_t) {
|
|
auto mr = no_null_memory_resource();
|
|
{ auto map = map_t(&mr); }
|
|
{
|
|
auto map = map_t(&mr);
|
|
for (size_t i = 0; i < 1; ++i) {
|
|
map[i] = i;
|
|
}
|
|
}
|
|
|
|
{
|
|
auto map = map_t(&mr);
|
|
for (size_t i = 0; i < 1; ++i) {
|
|
map[i] = i;
|
|
}
|
|
auto map2 = std::move(map);
|
|
}
|
|
}
|
|
|
|
void show([[maybe_unused]] track_peak_memory_resource const& mr, [[maybe_unused]] std::string_view name) {
|
|
// fmt::print("{}: {} allocs, {} deallocs, {} is_equals\n", name, mr.num_allocs(), mr.num_deallocs(), mr.num_is_equals());
|
|
}
|
|
|
|
// the following tests are vector specific, so don't use segmented_vector
|
|
|
|
TEST_CASE("pmr_copy") {
|
|
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
|
|
auto mr1 = track_peak_memory_resource();
|
|
auto map1 = map_t(&mr1);
|
|
map1[1] = 2;
|
|
|
|
auto mr2 = track_peak_memory_resource();
|
|
auto map2 = map_t(&mr2);
|
|
map2[3] = 4;
|
|
show(mr1, "mr1");
|
|
show(mr2, "mr2");
|
|
|
|
map1 = map2;
|
|
REQUIRE(map1.size() == 1);
|
|
REQUIRE(map1.find(3) != map1.end());
|
|
show(mr1, "mr1");
|
|
show(mr2, "mr2");
|
|
|
|
REQUIRE(mr1.num_allocs() == 3);
|
|
REQUIRE(mr1.num_deallocs() == 1);
|
|
REQUIRE(mr1.num_is_equals() == 0);
|
|
|
|
REQUIRE(mr2.num_allocs() == 2);
|
|
REQUIRE(mr2.num_deallocs() == 0);
|
|
REQUIRE(mr2.num_is_equals() == 0);
|
|
}
|
|
|
|
TEST_CASE("pmr_move_different_mr") {
|
|
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
|
|
|
|
auto mr1 = track_peak_memory_resource();
|
|
auto map1 = map_t(&mr1);
|
|
map1[1] = 2;
|
|
|
|
auto mr2 = track_peak_memory_resource();
|
|
auto map2 = map_t(&mr2);
|
|
map2[3] = 4;
|
|
show(mr1, "mr1");
|
|
show(mr2, "mr2");
|
|
|
|
map1 = std::move(map2);
|
|
REQUIRE(map1.size() == 1);
|
|
REQUIRE(map1.find(3) != map1.end());
|
|
show(mr1, "mr1");
|
|
show(mr2, "mr2");
|
|
|
|
REQUIRE(mr1.num_allocs() == 3);
|
|
REQUIRE(mr1.num_deallocs() == 1);
|
|
REQUIRE(mr1.num_is_equals() == 1);
|
|
|
|
REQUIRE(mr2.num_allocs() == 2);
|
|
REQUIRE(mr2.num_deallocs() == 0);
|
|
REQUIRE(mr2.num_is_equals() == 1);
|
|
}
|
|
|
|
TEST_CASE("pmr_move_same_mr") {
|
|
using map_t = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>;
|
|
|
|
auto mr1 = track_peak_memory_resource();
|
|
auto map1 = map_t(&mr1);
|
|
map1[1] = 2;
|
|
REQUIRE(mr1.num_allocs() == 2);
|
|
REQUIRE(mr1.num_deallocs() == 0);
|
|
REQUIRE(mr1.num_is_equals() == 0);
|
|
|
|
auto map2 = ankerl::unordered_dense::pmr::map<uint64_t, uint64_t>(&mr1);
|
|
map2[3] = 4;
|
|
show(mr1, "mr1");
|
|
|
|
map1 = std::move(map2);
|
|
REQUIRE(map1.size() == 1);
|
|
REQUIRE(map1.find(3) != map1.end());
|
|
show(mr1, "mr1");
|
|
|
|
REQUIRE(mr1.num_allocs() == 5); // 5 because of the initial allocation
|
|
REQUIRE(mr1.num_deallocs() == 2);
|
|
REQUIRE(mr1.num_is_equals() == 0);
|
|
}
|
|
|
|
# endif
|
|
#endif |