Просмотр исходного кода

sparse_set/storage:
* better performance on insertion
* exception guarantee for nothrow default constructible entity types

Michele Caini 4 лет назад
Родитель
Сommit
ff67f402bb
3 измененных файлов с 58 добавлено и 60 удалено
  1. 32 37
      src/entt/entity/sparse_set.hpp
  2. 24 19
      src/entt/entity/storage.hpp
  3. 2 4
      test/entt/entity/sparse_set.cpp

+ 32 - 37
src/entt/entity/sparse_set.hpp

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

+ 24 - 19
src/entt/entity/storage.hpp

@@ -179,24 +179,27 @@ class basic_storage: public basic_sparse_set<Entity, typename std::allocator_tra
         return pos & (page_size - 1);
         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) {
                 if(count) {
                     const auto sz = count > page_size ? page_size : count;
                     const auto sz = count > page_size ? page_size : count;
                     std::destroy(packed[pos], packed[pos] + sz);
                     std::destroy(packed[pos], packed[pos] + sz);
                     count -= sz;
                     count -= sz;
                 }
                 }
-            
+
                 alloc_traits::deallocate(allocator, packed[pos], page_size);
                 alloc_traits::deallocate(allocator, packed[pos], page_size);
                 bucket_alloc_traits::destroy(bucket_allocator, std::addressof(packed[pos]));
                 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));
             const auto old = std::exchange(packed, bucket_alloc_traits::allocate(bucket_allocator, bucket));
 
 
             if(bucket > length) {
             if(bucket > length) {
@@ -308,7 +311,7 @@ public:
 
 
     /*! @brief Default destructor. */
     /*! @brief Default destructor. */
     ~basic_storage() override {
     ~basic_storage() override {
-        maybe_resize_packed(0u);
+        release_memory();
     }
     }
 
 
     /**
     /**
@@ -317,7 +320,7 @@ public:
      * @return This sparse set.
      * @return This sparse set.
      */
      */
     basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
     basic_storage & operator=(basic_storage &&other) ENTT_NOEXCEPT {
-        maybe_resize_packed(0u);
+        release_memory();
 
 
         allocator = std::move(other.allocator);
         allocator = std::move(other.allocator);
         bucket_allocator = std::move(other.bucket_allocator);
         bucket_allocator = std::move(other.bucket_allocator);
@@ -356,7 +359,7 @@ public:
     /*! @brief Requests the removal of unused capacity. */
     /*! @brief Requests the removal of unused capacity. */
     void shrink_to_fit() {
     void shrink_to_fit() {
         underlying_type::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>
     template<typename It>
     void insert(It first, It last, const value_type &value = {}) {
     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);
             maybe_resize_packed(sz);
+            underlying_type::reserve(sz);
 
 
-            while(count != sz) {
+            for(; first != last; ++first) {
                 push_back(value);
                 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>>>
     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) {
     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);
             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);
             }
             }
         }
         }
     }
     }

+ 2 - 4
test/entt/entity/sparse_set.cpp

@@ -134,10 +134,8 @@ TEST(SparseSet, Insert) {
     entities[1] = entt::entity{42};
     entities[1] = entt::entity{42};
 
 
     set.emplace(entt::entity{12});
     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});
     set.emplace(entt::entity{24});
 
 
     ASSERT_TRUE(set.contains(entities[0]));
     ASSERT_TRUE(set.contains(entities[0]));