sparse_set/storage:

* better performance on insertion
* exception guarantee for nothrow default constructible entity types
This commit is contained in:
Michele Caini
2021-05-24 08:41:11 +02:00
parent f048a3f1d8
commit ff67f402bb
3 changed files with 58 additions and 60 deletions

View File

@@ -191,37 +191,35 @@ class basic_sparse_set {
return sparse[idx];
}
void maybe_release_pages() {
if(!count && bucket) {
void resize_packed(const std::size_t req) {
ENTT_ASSERT(req && (req != reserved) && !(req < count), "Invalid request");
auto old = std::exchange(packed, alloc_traits::allocate(allocator, req));
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, std::exchange(reserved, req));
}
void release_memory() {
if(reserved) {
std::destroy(packed, packed + std::exchange(count, 0u));
alloc_traits::deallocate(allocator, std::exchange(packed, alloc_pointer{}), std::exchange(reserved, 0u));
}
if(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));
}
}
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, reserved));
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);
bucket_alloc_traits::deallocate(bucket_allocator, sparse, std::exchange(bucket, 0u));
}
}
@@ -317,8 +315,7 @@ public:
/*! @brief Default destructor. */
virtual ~basic_sparse_set() {
maybe_resize_packed(0u);
maybe_release_pages();
release_memory();
}
/**
@@ -327,8 +324,7 @@ public:
* @return This sparse set.
*/
basic_sparse_set & operator=(basic_sparse_set &&other) ENTT_NOEXCEPT {
maybe_resize_packed(0u);
maybe_release_pages();
release_memory();
allocator = std::move(other.allocator);
bucket_allocator = std::move(other.bucket_allocator);
@@ -351,7 +347,7 @@ public:
*/
void reserve(const size_type cap) {
if(cap > reserved) {
maybe_resize_packed(cap);
resize_packed(cap);
}
}
@@ -366,8 +362,11 @@ public:
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
maybe_resize_packed(count);
maybe_release_pages();
if(!count && reserved) {
release_memory();
} else if(count && count != reserved) {
resize_packed(count);
}
}
/**
@@ -538,7 +537,7 @@ public:
if(count == reserved) {
const size_type sz(reserved * growth_factor);
maybe_resize_packed(sz + !(sz > reserved));
resize_packed(sz + !(sz > reserved));
}
push_back(entt);
@@ -554,19 +553,15 @@ public:
* @tparam It Type of input iterator.
* @param first An iterator to the first element of the range of entities.
* @param last An iterator past the last element of the range of entities.
* @return The number of elements assigned to the sparse set.
*/
template<typename It>
size_type insert(It first, It last) {
const auto length = std::distance(first, last);
reserve(count + length);
void insert(It first, It last) {
reserve(count + std::distance(first, last));
for(; first != last; ++first) {
ENTT_ASSERT(!contains(*first), "Set already contains entity");
push_back(*first);
}
return length;
}
/**

View File

@@ -179,24 +179,27 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
return pos & (page_size - 1);
}
void maybe_resize_packed(const std::size_t req) {
if(const auto length = std::exchange(bucket, (req + page_size - 1u) / page_size); !bucket) {
for(size_type pos{}; pos < length; ++pos) {
void release_memory() {
if(bucket) {
for(size_type pos{}; pos < bucket; ++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(length) {
bucket_alloc_traits::deallocate(bucket_allocator, packed, length);
}
} else if(bucket != length) {
ENTT_ASSERT(!(req < count), "Invalid request");
bucket_alloc_traits::deallocate(bucket_allocator, std::exchange(packed, bucket_alloc_pointer{}), std::exchange(bucket, 0u));
}
}
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 != length) {
const auto old = std::exchange(packed, bucket_alloc_traits::allocate(bucket_allocator, bucket));
if(bucket > length) {
@@ -308,7 +311,7 @@ public:
/*! @brief Default destructor. */
~basic_storage() override {
maybe_resize_packed(0u);
release_memory();
}
/**
@@ -317,7 +320,7 @@ public:
* @return This sparse set.
*/
basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
maybe_resize_packed(0u);
release_memory();
allocator = std::move(other.allocator);
bucket_allocator = std::move(other.bucket_allocator);
@@ -356,7 +359,7 @@ public:
/*! @brief Requests the removal of unused capacity. */
void shrink_to_fit() {
underlying_type::shrink_to_fit();
maybe_resize_packed(count);
count ? maybe_resize_packed(count) : release_memory();
}
/**
@@ -542,12 +545,13 @@ public:
*/
template<typename It>
void insert(It first, It last, const value_type &value = {}) {
if(const auto length = underlying_type::insert(first, last); length) {
const auto sz = count + length;
if(const auto sz = count + std::distance(first, last); sz != count) {
maybe_resize_packed(sz);
underlying_type::reserve(sz);
while(count != sz) {
for(; first != last; ++first) {
push_back(value);
underlying_type::emplace(*first);
}
}
}
@@ -566,12 +570,13 @@ public:
*/
template<typename EIt, typename CIt, typename = std::enable_if_t<std::is_same_v<std::decay_t<typename std::iterator_traits<CIt>::value_type>, value_type>>>
void insert(EIt first, EIt last, CIt from) {
if(const auto length = underlying_type::insert(first, last); length) {
const auto sz = count + length;
if(const auto sz = count + std::distance(first, last); sz != count) {
maybe_resize_packed(sz);
underlying_type::reserve(sz);
while(count != sz) {
push_back(*(from++));
for(; first != last; ++first, ++from) {
push_back(*from);
underlying_type::emplace(*first);
}
}
}

View File

@@ -134,10 +134,8 @@ TEST(SparseSet, Insert) {
entities[1] = entt::entity{42};
set.emplace(entt::entity{12});
ASSERT_EQ(set.insert(std::end(entities), std::end(entities)), 0u);
ASSERT_EQ(set.insert(std::begin(entities), std::end(entities)), 2u);
set.insert(std::end(entities), std::end(entities));
set.insert(std::begin(entities), std::end(entities));
set.emplace(entt::entity{24});
ASSERT_TRUE(set.contains(entities[0]));