sparse_set/storage: small review and optimizations

This commit is contained in:
Michele Caini
2021-05-19 09:14:13 +02:00
parent bc86576aa8
commit 3d9959c8e5
4 changed files with 90 additions and 80 deletions

View File

@@ -191,32 +191,55 @@ class basic_sparse_set {
return sparse[idx];
}
void resize_packed(const std::size_t req) {
ENTT_ASSERT(req && !(req < count), "Invalid request");
auto old = std::exchange(packed, alloc_traits::allocate(allocator, req));
void maybe_release_pages() {
if(!count && bucket) {
for(size_type pos{}; pos < bucket; ++pos) {
if(sparse[pos]) {
std::destroy(sparse[pos], sparse[pos] + page_size);
alloc_traits::deallocate(allocator, sparse[pos], page_size);
}
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(sparse[pos]));
}
bucket_alloc_traits::deallocate(bucket_allocator, sparse, std::exchange(bucket, 0u));
}
}
[[nodiscard]] std::size_t packed_size_for(const std::size_t req) {
auto sz = reserved;
while(req > sz) {
const size_type next(sz * growth_factor);
sz = next + !(next > sz);
}
return sz;
}
void maybe_resize_packed(const std::size_t req) {
if(const auto length = std::exchange(reserved, req); !reserved) {
if(length) {
std::destroy(packed, packed + std::exchange(count, 0u));
alloc_traits::deallocate(allocator, packed, length);
}
} else if(reserved != length) {
ENTT_ASSERT(!(req < count), "Invalid request");
auto old = std::exchange(packed, alloc_traits::allocate(allocator, req));
if(const auto length = std::exchange(reserved, req); length) {
for(size_type pos{}; pos < count; ++pos) {
alloc_traits::construct(allocator, std::addressof(packed[pos]), std::move(old[pos]));
alloc_traits::destroy(allocator, std::addressof(old[pos]));
}
alloc_traits::deallocate(allocator, old, length);
}
}
template<typename It>
void push_back(It first, It last) {
if(const size_type req = count + std::distance(first, last); reserved < req) {
const size_type sz = size_type(reserved * growth_factor) + (reserved == 0u);
resize_packed(sz < req ? req : sz);
}
for(; first != last; ++first) {
ENTT_ASSERT(!contains(*first), "Set already contains entity");
assure_page(page(*first))[offset(*first)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
alloc_traits::construct(allocator, std::addressof(packed[count++]), *first);
}
void push_back(const Entity entt) {
ENTT_ASSERT(count != reserved, "No more space left");
assure_page(page(entt))[offset(entt)] = entity_type{static_cast<typename traits_type::entity_type>(count)};
alloc_traits::construct(allocator, std::addressof(packed[count++]), entt);
}
void pop(const Entity entt, void *ud) {
@@ -238,26 +261,6 @@ class basic_sparse_set {
swap_and_pop(pos);
}
void reset_to_empty() {
if(const auto length = std::exchange(reserved, 0u); length) {
std::destroy(packed, packed + std::exchange(count, 0u));
alloc_traits::deallocate(allocator, packed, length);
}
if(const auto length = std::exchange(bucket, 0u); length) {
for(size_type pos{}; pos < length; ++pos) {
if(sparse[pos]) {
std::destroy(sparse[pos], sparse[pos] + page_size);
alloc_traits::deallocate(allocator, sparse[pos], page_size);
}
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(sparse[pos]));
}
bucket_alloc_traits::deallocate(bucket_allocator, sparse, std::exchange(bucket, 0u));
}
}
protected:
/**
* @brief Swaps two entities in the internal packed array.
@@ -323,7 +326,8 @@ public:
/*! @brief Default destructor. */
virtual ~basic_sparse_set() {
reset_to_empty();
maybe_resize_packed(0u);
maybe_release_pages();
}
/**
@@ -352,7 +356,7 @@ public:
*/
void reserve(const size_type cap) {
if(cap > reserved) {
resize_packed(cap);
maybe_resize_packed(cap);
}
}
@@ -367,11 +371,8 @@ public:
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
if(!count) {
reset_to_empty();
} else if(reserved > count) {
resize_packed(count);
}
maybe_resize_packed(count);
maybe_release_pages();
}
/**
@@ -538,8 +539,9 @@ public:
* @param entt A valid entity identifier.
*/
void emplace(const entity_type entt) {
entity_type arr[1u]{entt};
push_back(arr, arr + 1u);
ENTT_ASSERT(!contains(entt), "Set already contains entity");
maybe_resize_packed(packed_size_for(count + 1u));
push_back(entt);
}
/**
@@ -555,7 +557,12 @@ public:
*/
template<typename It>
void insert(It first, It last) {
push_back(first, last);
maybe_resize_packed(packed_size_for(count + std::distance(first, last)));
for(; first != last; ++first) {
ENTT_ASSERT(!contains(*first), "Set already contains entity");
push_back(*first);
}
}
/**

View File

@@ -180,21 +180,31 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
}
void maybe_resize_packed(const std::size_t req) {
ENTT_ASSERT(req && !(req < count), "Invalid request");
if(const auto length = std::exchange(bucket, (req + page_size - 1u) / page_size); !bucket) {
for(size_type pos{}; pos < length; ++pos) {
if(count) {
const auto sz = count > page_size ? page_size : count;
std::destroy(packed[pos], packed[pos] + sz);
count -= sz;
}
alloc_traits::deallocate(allocator, packed[pos], page_size);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
}
if(const auto length = std::exchange(bucket, (req + page_size - 1u) / page_size); bucket != length) {
if(length) {
bucket_alloc_traits::deallocate(bucket_allocator, packed, length);
}
} else if(bucket != length) {
ENTT_ASSERT(!(req < count), "Invalid request");
const auto old = std::exchange(packed, bucket_alloc_traits::allocate(bucket_allocator, bucket));
if(bucket > length) {
if(length) {
for(size_type pos{}; pos < length; ++pos) {
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
}
bucket_alloc_traits::deallocate(bucket_allocator, old, length);
for(size_type pos{}; pos < length; ++pos) {
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
}
for(auto pos = length; pos < bucket; ++pos) {
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), alloc_traits::allocate(allocator, page_size));
}
@@ -203,12 +213,14 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
bucket_alloc_traits::construct(bucket_allocator, std::addressof(packed[pos]), old[pos]);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
}
for(auto pos = bucket; pos < length; ++pos) {
alloc_traits::deallocate(allocator, old[pos], page_size);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(old[pos]));
}
}
if(length) {
bucket_alloc_traits::deallocate(bucket_allocator, old, length);
}
}
@@ -228,23 +240,6 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
return ref;
}
void reset_to_empty() {
if(const auto length = std::exchange(bucket, 0u); length) {
for(size_type pos{}; pos < length; ++pos) {
if(count) {
const auto sz = count > page_size ? page_size : count;
std::destroy(packed[pos], packed[pos] + sz);
count -= sz;
}
alloc_traits::deallocate(allocator, packed[pos], page_size);
bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
}
bucket_alloc_traits::deallocate(bucket_allocator, packed, length);
}
}
protected:
/*! @copydoc basic_sparse_set::swap_at */
void swap_at(const std::size_t lhs, const std::size_t rhs) final {
@@ -255,12 +250,10 @@ protected:
void swap_and_pop(const std::size_t pos) final {
auto &&elem = packed[page(pos)][offset(pos)];
[[maybe_unused]] auto other = std::move(elem);
auto &&last = (--count, packed[page(count)][offset(count)]);
auto &&last = packed[page(count - 1)][offset(count - 1)];
elem = std::move(last);
alloc_traits::destroy(allocator, std::addressof(last));
--count;
}
public:
@@ -312,7 +305,7 @@ public:
/*! @brief Default destructor. */
~basic_storage() override {
reset_to_empty();
maybe_resize_packed(0u);
}
/**
@@ -357,7 +350,7 @@ public:
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
underlying_type::shrink_to_fit();
count ? maybe_resize_packed(count) : reset_to_empty();
maybe_resize_packed(count);
}
/**

View File

@@ -24,6 +24,11 @@ TEST(SparseSet, Functionalities) {
ASSERT_FALSE(set.contains(entt::entity{0}));
ASSERT_FALSE(set.contains(entt::entity{42}));
set.reserve(0);
ASSERT_EQ(set.capacity(), 42u);
ASSERT_TRUE(set.empty());
set.emplace(entt::entity{42});
ASSERT_FALSE(set.empty());

View File

@@ -48,6 +48,11 @@ TEST(Storage, Functionalities) {
ASSERT_FALSE(pool.contains(entt::entity{0}));
ASSERT_FALSE(pool.contains(entt::entity{41}));
pool.reserve(0);
ASSERT_EQ(pool.capacity(), entt::page_size);
ASSERT_TRUE(pool.empty());
pool.emplace(entt::entity{41}, 3);
ASSERT_FALSE(pool.empty());