#include #include #include #include // for size_t #include // for uint64_t #include // thrown in no_null_memory_resource #include // for string_view #include // for move #include // 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; 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; 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; 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(&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