diff --git a/include/libpmr/new.h b/include/libpmr/new.h index 73b3c8d..6973ce0 100644 --- a/include/libpmr/new.h +++ b/include/libpmr/new.h @@ -14,6 +14,7 @@ #include "libimp/uninitialized.h" #include "libimp/byte.h" #include "libimp/detect_plat.h" +#include "libimp/export.h" #include "libpmr/def.h" #include "libpmr/block_pool.h" @@ -21,6 +22,7 @@ LIBPMR_NAMESPACE_BEG_ +/// \brief Select the incremental level based on the size. constexpr inline std::size_t regular_level(std::size_t s) noexcept { return (s <= 128 ) ? 0 : (s <= 1024 ) ? 1 : @@ -28,6 +30,7 @@ constexpr inline std::size_t regular_level(std::size_t s) noexcept { (s <= 65536) ? 3 : 4; } +/// \brief Calculates the appropriate memory block size based on the increment level and size. constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) noexcept { return (l == 0) ? std::max(::LIBIMP::round_up(s, 8), regular_head_size) : (l == 1) ? ::LIBIMP::round_up(s, 128 ) : @@ -35,54 +38,51 @@ constexpr inline std::size_t regular_sizeof_impl(std::size_t l, std::size_t s) n (l == 3) ? ::LIBIMP::round_up(s, 8192) : (std::numeric_limits::max)(); } +/// \brief Calculates the appropriate memory block size based on the size. constexpr inline std::size_t regular_sizeof(std::size_t s) noexcept { return regular_sizeof_impl(regular_level(s), s); } +/// \brief Calculates the appropriate memory block size based on the specific type. template constexpr inline std::size_t regular_sizeof() noexcept { return regular_sizeof(regular_head_size + sizeof(T)); } -class block_pool_like { +/// \brief Defines the memory block collector interface. +class LIBIMP_EXPORT block_collector { public: - virtual ~block_pool_like() noexcept = default; - virtual void *allocate() noexcept = 0; + virtual ~block_collector() noexcept = default; virtual void deallocate(void *p) noexcept = 0; }; -inline std::unordered_map &get_block_pool_map() noexcept { - thread_local std::unordered_map instances; - return instances; -} +/// \brief Gets all block pools of the thread cache. +LIBIMP_EXPORT auto get_thread_block_pool_map() noexcept + -> std::unordered_map &; +/// \brief Defines block pool memory resource based on block pool. template class block_pool_resource; +/// \brief Memory block collector of unknown size. +/// \note This memory resource is only used to temporarily collect memory blocks +/// that cannot find a suitable block pool memory resource. template <> class block_pool_resource<0, 0> : public block_pool<0, 0> - , public block_pool_like { - - void *allocate() noexcept override { - return nullptr; - } - + , public block_collector { public: void deallocate(void *p) noexcept override { block_pool<0, 0>::deallocate(p); } }; +/// \brief A block pool memory resource for a block of memory of a specific size. template class block_pool_resource : public block_pool - , public block_pool_like { + , public block_collector { using base_t = block_pool; - void *allocate() noexcept override { - return base_t::allocate(); - } - void deallocate(void *p) noexcept override { base_t::deallocate(p); } @@ -105,7 +105,9 @@ public: base_t::deallocate(p); return; } - auto &map = get_block_pool_map(); + // When the actual size exceeds the current memory block size, + // try to find a suitable pool among all memory block pools for this thread. + auto &map = get_thread_block_pool_map(); auto it = map.find(r_size); if ((it == map.end()) || (it->second == nullptr)) { block_pool_resource<0, 0> *bp = nullptr; @@ -128,13 +130,16 @@ public: template auto block_pool_resource::get() noexcept -> block_pool_resource * { - auto &map = get_block_pool_map(); thread_local block_pool_resource *pi = nullptr; if (pi != nullptr) { return pi; } + // Create a new block pool resource for this thread. + auto &map = get_thread_block_pool_map(); auto it = map.find(BlockSize); if ((it != map.end()) && (it->second != nullptr)) { + // If there are existing block pool resources in the thread cache, + // a new block pool resource is constructed based on it and the cache is updated. auto *bp = static_cast *>( dynamic_cast *>(it->second)); if (bp == nullptr) { @@ -142,34 +147,35 @@ auto block_pool_resource::get() noexcept } thread_local block_pool_resource instance(std::move(*bp)); delete static_cast *>(bp); - pi = &instance; + it->second = pi = &instance; + return pi; } else { + // If there are no existing block pool resources in the thread cache, + // the thread local storage instance is constructed and the pointer is cached. thread_local block_pool_resource instance; - pi = &instance; + LIBIMP_TRY { + map.emplace(BlockSize, pi = &instance); + return pi; + } LIBIMP_CATCH(...) { + return nullptr; + } } - LIBIMP_TRY { - map.emplace(BlockSize, pi); - } LIBIMP_CATCH(...) { - return nullptr; - } - return pi; } +/// \brief Match the appropriate memory block resources +/// according to the size of the specification. template class regular_resource : public new_delete_resource {}; -template -class regular_resource : public block_pool_resource {}; - -template -class regular_resource : public block_pool_resource {}; - -template -class regular_resource : public block_pool_resource {}; - -template -class regular_resource : public block_pool_resource {}; +/// \brief Different increment levels match different chunk sizes. +/// 512 means that 512 consecutive memory blocks are allocated at a time, and the block size is N. +template class regular_resource : public block_pool_resource {}; +template class regular_resource : public block_pool_resource {}; +template class regular_resource : public block_pool_resource {}; +template class regular_resource : public block_pool_resource {}; +/// \brief Creates an object based on the specified type and parameters with block pool resource. +/// \note This function is thread-safe. template T *new$(A &&... args) noexcept { auto *mem_res = regular_resource()>::get(); @@ -177,6 +183,9 @@ T *new$(A &&... args) noexcept { return ::LIBIMP::construct(mem_res->allocate(sizeof(T), alignof(T)), std::forward(args)...); } +/// \brief Destroys object previously allocated by the `new$` and releases obtained memory area. +/// \note This function is thread-safe. If the pointer type passed in is different from `new$`, +/// additional performance penalties may be incurred. template void delete$(T *p) noexcept { if (p == nullptr) return; @@ -184,6 +193,7 @@ void delete$(T *p) noexcept { auto *mem_res = regular_resource()>::get(); if (mem_res == nullptr) return; #if defined(LIBIMP_CC_MSVC_2015) + // `alignof` of vs2015 requires that type must be able to be instantiated. mem_res->deallocate(p, sizeof(T)); #else mem_res->deallocate(p, sizeof(T), alignof(T)); diff --git a/src/libpmr/new.cpp b/src/libpmr/new.cpp new file mode 100644 index 0000000..db9c1aa --- /dev/null +++ b/src/libpmr/new.cpp @@ -0,0 +1,12 @@ + +#include "libpmr/new.h" + +LIBPMR_NAMESPACE_BEG_ + +auto get_thread_block_pool_map() noexcept + -> std::unordered_map & { + thread_local std::unordered_map instances; + return instances; +} + +LIBPMR_NAMESPACE_END_