sparse_set: used compressed pair to hide the cost of storing allocators

This commit is contained in:
Michele Caini
2021-07-06 16:04:20 +02:00
parent ed2c714419
commit fbc9595f12

View File

@@ -9,6 +9,7 @@
#include <utility>
#include "../config/config.h"
#include "../core/algorithm.hpp"
#include "../core/compressed_pair.hpp"
#include "../core/fwd.hpp"
#include "entity.hpp"
#include "fwd.hpp"
@@ -65,9 +66,6 @@ class basic_sparse_set {
using alloc_ptr_traits = typename std::allocator_traits<alloc_ptr>;
using alloc_ptr_pointer = typename alloc_ptr_traits::pointer;
// TODO using bucket_alloc_traits = typename std::allocator_traits<Allocator>::template rebind_traits<alloc_pointer>;
// TODO using bucket_alloc_pointer = typename bucket_alloc_traits::pointer;
using entity_traits = entt_traits<Entity>;
static_assert(alloc_traits::propagate_on_container_move_assignment::value);
@@ -181,7 +179,7 @@ class basic_sparse_set {
[[nodiscard]] auto assure_page(const std::size_t idx) {
if(!(idx < bucket)) {
const size_type sz = idx + 1u;
alloc_ptr allocator_ptr{allocator};
alloc_ptr allocator_ptr{reserved.first()};
const auto mem = alloc_ptr_traits::allocate(allocator_ptr, sz);
std::uninitialized_value_construct(mem + bucket, mem + sz);
@@ -195,7 +193,7 @@ class basic_sparse_set {
}
if(!sparse[idx]) {
sparse[idx] = alloc_traits::allocate(allocator, sparse_page);
sparse[idx] = alloc_traits::allocate(reserved.first(), sparse_page);
std::uninitialized_fill(sparse[idx], sparse[idx] + sparse_page, null);
}
@@ -203,21 +201,24 @@ class basic_sparse_set {
}
void resize_packed(const std::size_t req) {
ENTT_ASSERT((req != reserved) && !(req < count), "Invalid request");
auto &&[allocator, len] = reserved;
ENTT_ASSERT((req != len) && !(req < count), "Invalid request");
const auto mem = alloc_traits::allocate(allocator, req);
std::uninitialized_copy(packed, packed + count, mem);
std::uninitialized_fill(mem + count, mem + req, tombstone);
std::destroy(packed, packed + reserved);
alloc_traits::deallocate(allocator, packed, reserved);
std::destroy(packed, packed + len);
alloc_traits::deallocate(allocator, packed, len);
packed = mem;
reserved = req;
len = req;
}
void release_memory() {
if(packed) {
auto &&[allocator, len] = reserved;
for(size_type pos{}; pos < bucket; ++pos) {
if(sparse[pos]) {
std::destroy(sparse[pos], sparse[pos] + sparse_page);
@@ -225,11 +226,11 @@ class basic_sparse_set {
}
}
std::destroy(packed, packed + reserved);
std::destroy(packed, packed + len);
std::destroy(sparse, sparse + bucket);
alloc_ptr allocator_ptr{allocator};
alloc_traits::deallocate(allocator, packed, reserved);
alloc_traits::deallocate(allocator, packed, len);
alloc_ptr_traits::deallocate(allocator_ptr, sparse, bucket);
}
}
@@ -303,12 +304,11 @@ public:
* @param alloc Allocator to use (possibly default-constructed).
*/
explicit basic_sparse_set(deletion_policy pol, const allocator_type &alloc = {})
: allocator{alloc},
sparse{alloc_ptr_traits::allocate(alloc_ptr{allocator}, 0u)},
packed{alloc_traits::allocate(allocator, 0u)},
: reserved{alloc, 0u},
sparse{alloc_ptr_traits::allocate(alloc_ptr{reserved.first()}, 0u)},
packed{alloc_traits::allocate(reserved.first(), 0u)},
bucket{0u},
count{0u},
reserved{0u},
free_list{tombstone},
mode{pol}
{}
@@ -326,12 +326,11 @@ public:
* @param other The instance to move from.
*/
basic_sparse_set(basic_sparse_set &&other) ENTT_NOEXCEPT
: allocator{std::move(other.allocator)},
: reserved{std::move(other.reserved)},
sparse{std::exchange(other.sparse, alloc_ptr_pointer{})},
packed{std::exchange(other.packed, alloc_pointer{})},
bucket{std::exchange(other.bucket, 0u)},
count{std::exchange(other.count, 0u)},
reserved{std::exchange(other.reserved, 0u)},
free_list{std::exchange(other.free_list, tombstone)},
mode{other.mode}
{}
@@ -349,12 +348,11 @@ public:
basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
release_memory();
allocator = std::move(other.allocator);
reserved = std::move(other.reserved);
sparse = std::exchange(other.sparse, alloc_ptr_pointer{});
packed = std::exchange(other.packed, alloc_pointer{});
bucket = std::exchange(other.bucket, 0u);
count = std::exchange(other.count, 0u);
reserved = std::exchange(other.reserved, 0u);
free_list = std::exchange(other.free_list, tombstone);
mode = other.mode;
@@ -366,7 +364,7 @@ public:
* @return The associated allocator.
*/
[[nodiscard]] constexpr allocator_type get_allocator() const ENTT_NOEXCEPT {
return allocator_type{allocator};
return allocator_type{reserved.first()};
}
/**
@@ -394,7 +392,7 @@ public:
* @param cap Desired capacity.
*/
void reserve(const size_type cap) {
if(cap > reserved) {
if(cap > reserved.second()) {
resize_packed(cap);
}
}
@@ -405,12 +403,12 @@ public:
* @return Capacity of the sparse set.
*/
[[nodiscard]] size_type capacity() const ENTT_NOEXCEPT {
return reserved;
return reserved.second();
}
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
if(count < reserved) {
if(count < reserved.second()) {
resize_packed(count);
}
}
@@ -583,9 +581,9 @@ public:
size_type emplace_back(const entity_type entt) {
ENTT_ASSERT(!contains(entt), "Set already contains entity");
if(count == reserved) {
const size_type sz = static_cast<size_type>(reserved * growth_factor);
resize_packed(sz + !(sz > reserved));
if(const auto len = reserved.second(); count == len) {
const size_type sz = static_cast<size_type>(len * growth_factor);
resize_packed(sz + !(sz > len));
}
assure_page(page(entt))[offset(entt)] = entity_traits::construct(static_cast<typename entity_traits::entity_type>(count));
@@ -867,12 +865,11 @@ public:
}
private:
alloc allocator;
compressed_pair<alloc, size_type> reserved;
alloc_ptr_pointer sparse;
alloc_pointer packed;
std::size_t bucket;
std::size_t count;
std::size_t reserved;
size_type bucket;
size_type count;
entity_type free_list;
deletion_policy mode;
};