osrm-backend/third_party/unordered_dense/test/unit/pmr.cpp

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