Browse Source

any: avoid vtable calls to release trivially destructible objects (perf)

skypjack 4 months ago
parent
commit
e18061bf86
2 changed files with 49 additions and 35 deletions
  1. 1 0
      TODO
  2. 48 35
      src/entt/core/any.hpp

+ 1 - 0
TODO

@@ -35,3 +35,4 @@ TODO:
 * trivially destructible optimization for basic_any
 * introduce compile-time flag to skip dll-only checks in basic_any
 * auto policy for return types in meta (i.e. it picks as_is or as_(c)ref automatically as needed)
+* find a way to test deleter and the absence of it in basic_any

+ 48 - 35
src/entt/core/any.hpp

@@ -20,7 +20,6 @@ enum class any_request : std::uint8_t {
     info,
     transfer,
     assign,
-    destroy,
     compare,
     copy,
     move
@@ -60,8 +59,9 @@ struct in_situ<void, Len, Align>: std::false_type {};
 template<std::size_t Len, std::size_t Align>
 class basic_any: private internal::basic_any_storage<Len, Align> {
     using request = internal::any_request;
-    using vtable_type = const void *(const request, const basic_any &, const void *);
     using base_type = internal::basic_any_storage<Len, Align>;
+    using vtable_type = const void *(const request, const basic_any &, const void *);
+    using deleter_type = void(const basic_any &);
 
     template<typename Type>
     static constexpr bool in_situ_v = internal::in_situ<Type, Len, Align>::value;
@@ -70,15 +70,7 @@ class basic_any: private internal::basic_any_storage<Len, Align> {
     static const void *basic_vtable(const request req, const basic_any &value, const void *other) {
         static_assert(std::is_same_v<std::remove_const_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
 
-        const Type *elem = nullptr;
-
-        if constexpr(in_situ_v<Type>) {
-            elem = (value.mode == any_policy::embedded) ? reinterpret_cast<const Type *>(&value.buffer) : static_cast<const Type *>(value.instance);
-        } else {
-            elem = static_cast<const Type *>(value.instance);
-        }
-
-        switch(req) {
+        switch(const auto *elem = static_cast<const Type *>(value.data()); req) {
         case request::info:
             return &type_id<Type>();
         case request::transfer:
@@ -94,15 +86,6 @@ class basic_any: private internal::basic_any_storage<Len, Align> {
                 return other;
             }
             break;
-        case request::destroy:
-            if constexpr(in_situ_v<Type>) {
-                (value.mode == any_policy::embedded) ? elem->~Type() : (delete elem);
-            } else if constexpr(std::is_array_v<Type>) {
-                delete[] elem;
-            } else if constexpr(!std::is_void_v<Type>) {
-                delete elem;
-            }
-            break;
         case request::compare:
             if constexpr(!std::is_function_v<Type> && !std::is_array_v<Type> && is_equality_comparable_v<Type>) {
                 return (*elem == *static_cast<const Type *>(other)) ? other : nullptr;
@@ -126,6 +109,22 @@ class basic_any: private internal::basic_any_storage<Len, Align> {
         return nullptr;
     }
 
+    template<typename Type>
+    static void basic_deleter(const basic_any &value) {
+        static_assert(std::is_same_v<std::remove_const_t<std::remove_reference_t<Type>>, Type>, "Invalid type");
+        ENTT_ASSERT((value.mode == any_policy::dynamic) || ((value.mode == any_policy::embedded) && !std::is_trivially_destructible_v<Type>), "Unexpected policy");
+
+        const auto *elem = static_cast<const Type *>(value.data());
+
+        if constexpr(in_situ_v<Type>) {
+            (value.mode == any_policy::embedded) ? elem->~Type() : (delete elem);
+        } else if constexpr(std::is_array_v<Type>) {
+            delete[] elem;
+        } else {
+            delete elem;
+        }
+    }
+
     template<typename Type, typename... Args>
     void initialize([[maybe_unused]] Args &&...args) {
         using plain_type = std::remove_const_t<std::remove_reference_t<Type>>;
@@ -133,14 +132,22 @@ class basic_any: private internal::basic_any_storage<Len, Align> {
         vtable = basic_vtable<plain_type>;
 
         if constexpr(std::is_void_v<Type>) {
+            deleter = nullptr;
             mode = any_policy::empty;
             this->instance = nullptr;
         } else if constexpr(std::is_lvalue_reference_v<Type>) {
+            deleter = nullptr;
             static_assert((std::is_lvalue_reference_v<Args> && ...) && (sizeof...(Args) == 1u), "Invalid arguments");
             mode = std::is_const_v<std::remove_reference_t<Type>> ? any_policy::cref : any_policy::ref;
             // NOLINTNEXTLINE(bugprone-multi-level-implicit-pointer-conversion)
             this->instance = (std::addressof(args), ...);
         } else if constexpr(in_situ_v<plain_type>) {
+            if constexpr(std::is_trivially_destructible_v<plain_type>) {
+                deleter = nullptr;
+            } else {
+                deleter = &basic_deleter<plain_type>;
+            }
+
             mode = any_policy::embedded;
 
             if constexpr(std::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<plain_type>)) {
@@ -150,6 +157,7 @@ class basic_any: private internal::basic_any_storage<Len, Align> {
                 ::new(&this->buffer) plain_type(std::forward<Args>(args)...);
             }
         } else {
+            deleter = &basic_deleter<plain_type>;
             mode = any_policy::dynamic;
 
             if constexpr(std::is_aggregate_v<plain_type> && (sizeof...(Args) != 0u || !std::is_default_constructible_v<plain_type>)) {
@@ -163,14 +171,9 @@ class basic_any: private internal::basic_any_storage<Len, Align> {
         }
     }
 
-    basic_any(const basic_any &other, const any_policy pol) noexcept
-        : base_type{other.data()},
-          vtable{other.vtable},
-          mode{pol} {}
-
-    void destroy_if_owner() {
-        if(owner()) {
-            vtable(request::destroy, *this, nullptr);
+    void invoke_deleter_if_exists() {
+        if(deleter != nullptr) {
+            deleter(*this);
         }
     }
 
@@ -210,6 +213,7 @@ public:
             initialize<void>();
         } else {
             initialize<Type &>(*value);
+            deleter = &basic_deleter<Type>;
             mode = any_policy::dynamic;
         }
     }
@@ -239,6 +243,7 @@ public:
     basic_any(basic_any &&other) noexcept
         : base_type{},
           vtable{other.vtable},
+          deleter{other.deleter},
           mode{other.mode} {
         if(other.mode == any_policy::embedded) {
             other.vtable(request::move, other, this);
@@ -249,7 +254,7 @@ public:
 
     /*! @brief Frees the internal buffer, whatever it means. */
     ~basic_any() {
-        destroy_if_owner();
+        invoke_deleter_if_exists();
     }
 
     /**
@@ -259,7 +264,7 @@ public:
      */
     basic_any &operator=(const basic_any &other) {
         if(this != &other) {
-            destroy_if_owner();
+            invoke_deleter_if_exists();
 
             if(other) {
                 other.vtable(request::copy, other, this);
@@ -278,7 +283,7 @@ public:
      */
     basic_any &operator=(basic_any &&other) noexcept {
         if(this != &other) {
-            destroy_if_owner();
+            invoke_deleter_if_exists();
 
             if(other.mode == any_policy::embedded) {
                 other.vtable(request::move, other, this);
@@ -287,6 +292,7 @@ public:
             }
 
             vtable = other.vtable;
+            deleter = other.deleter;
             mode = other.mode;
         }
 
@@ -422,7 +428,7 @@ public:
      */
     template<typename Type, typename... Args>
     void emplace(Args &&...args) {
-        destroy_if_owner();
+        invoke_deleter_if_exists();
         initialize<Type>(std::forward<Args>(args)...);
     }
 
@@ -457,7 +463,7 @@ public:
 
     /*! @brief Destroys contained object */
     void reset() {
-        destroy_if_owner();
+        invoke_deleter_if_exists();
         initialize<void>();
     }
 
@@ -497,12 +503,18 @@ public:
      * @return A wrapper that shares a reference to an unmanaged object.
      */
     [[nodiscard]] basic_any as_ref() noexcept {
-        return basic_any{*this, (mode == any_policy::cref ? any_policy::cref : any_policy::ref)};
+        basic_any other = std::as_const(*this).as_ref();
+        other.mode = (mode == any_policy::cref ? any_policy::cref : any_policy::ref);
+        return other;
     }
 
     /*! @copydoc as_ref */
     [[nodiscard]] basic_any as_ref() const noexcept {
-        return basic_any{*this, any_policy::cref};
+        basic_any other{};
+        other.instance = data();
+        other.vtable = vtable;
+        other.mode = any_policy::cref;
+        return other;
     }
 
     /**
@@ -523,6 +535,7 @@ public:
 
 private:
     vtable_type *vtable{};
+    deleter_type *deleter{};
     any_policy mode{};
 };