From 4cdfc188881c863ba3505defc28ca85839af6415 Mon Sep 17 00:00:00 2001 From: Vadim Markovtsev Date: Thu, 24 Nov 2022 15:13:19 +0100 Subject: [PATCH 1/2] Refactor C++ allocators to be DRY and protected --- include/mimalloc.h | 147 ++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 88 deletions(-) diff --git a/include/mimalloc.h b/include/mimalloc.h index 86856900..8fd2eedd 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -414,7 +414,7 @@ mi_decl_nodiscard mi_decl_export mi_decl_restrict void* mi_heap_alloc_new_n(mi_h #include // std::forward #endif -template struct mi_stl_allocator { +template struct _mi_stl_allocator_common { typedef T value_type; typedef std::size_t size_type; typedef std::ptrdiff_t difference_type; @@ -422,6 +422,25 @@ template struct mi_stl_allocator { typedef value_type const& const_reference; typedef value_type* pointer; typedef value_type const* const_pointer; + + #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 + using propagate_on_container_copy_assignment = std::true_type; + using propagate_on_container_move_assignment = std::true_type; + using propagate_on_container_swap = std::true_type; + template void construct(U* p, Args&& ...args) { ::new(p) U(std::forward(args)...); } + template void destroy(U* p) mi_attr_noexcept { p->~U(); } + #else + void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } + void destroy(pointer p) { p->~value_type(); } + #endif + + size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX/sizeof(value_type)); } + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } +}; + +template struct mi_stl_allocator : public _mi_stl_allocator_common { + using typename _mi_stl_allocator_common::size_type; template struct rebind { typedef mi_stl_allocator other; }; mi_stl_allocator() mi_attr_noexcept = default; @@ -438,20 +457,8 @@ template struct mi_stl_allocator { #endif #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 - using propagate_on_container_copy_assignment = std::true_type; - using propagate_on_container_move_assignment = std::true_type; - using propagate_on_container_swap = std::true_type; - using is_always_equal = std::true_type; - template void construct(U* p, Args&& ...args) { ::new(p) U(std::forward(args)...); } - template void destroy(U* p) mi_attr_noexcept { p->~U(); } - #else - void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } - void destroy(pointer p) { p->~value_type(); } + using is_always_equal = std::true_type; #endif - - size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX/sizeof(value_type)); } - pointer address(reference x) const { return &x; } - const_pointer address(const_reference x) const { return &x; } }; template bool operator==(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return true; } @@ -460,26 +467,16 @@ template bool operator!=(const mi_stl_allocator& , const #if (__cplusplus >= 201103L) || (_MSC_VER > 1900) // C++11 #include // std::shared_ptr -// STL allocator allocation in a specific heap -template struct mi_heap_stl_allocator { - typedef T value_type; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - typedef value_type& reference; - typedef value_type const& const_reference; - typedef value_type* pointer; - typedef value_type const* const_pointer; - template struct rebind { typedef mi_heap_stl_allocator other; }; +template struct _mi_heap_stl_allocator_common : public _mi_stl_allocator_common { + using typename _mi_stl_allocator_common::size_type; - mi_heap_stl_allocator() { + _mi_heap_stl_allocator_common() { mi_heap_t* hp = mi_heap_new(); this->heap.reset(hp, heap_delete); } - mi_heap_stl_allocator(mi_heap_t* hp) : heap(hp) { } /* will not delete or destroy the passed in heap */ - mi_heap_stl_allocator(const mi_heap_stl_allocator& x) mi_attr_noexcept : heap(x.heap) { } - template mi_heap_stl_allocator(const mi_heap_stl_allocator& x) mi_attr_noexcept : heap(x.heap) { } - mi_heap_stl_allocator select_on_container_copy_construction() const { return *this; } - void deallocate(T* p, size_type) { mi_free(p); } + _mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp) { } /* will not delete or destroy the passed in heap */ + _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } + template _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } #if (__cplusplus >= 201703L) // C++17 mi_decl_nodiscard T* allocate(size_type count) { return static_cast(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); } @@ -489,84 +486,58 @@ template struct mi_heap_stl_allocator { #endif #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 - using propagate_on_container_copy_assignment = std::true_type; - using propagate_on_container_move_assignment = std::true_type; - using propagate_on_container_swap = std::true_type; using is_always_equal = std::false_type; - template void construct(U* p, Args&& ...args) { ::new(p) U(std::forward(args)...); } - template void destroy(U* p) mi_attr_noexcept { p->~U(); } - #else - void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } - void destroy(pointer p) { p->~value_type(); } #endif - size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX / sizeof(value_type)); } - pointer address(reference x) const { return &x; } - const_pointer address(const_reference x) const { return &x; } - void collect(bool force) { mi_heap_collect(this->heap.get(), force); } -// protected: +protected: std::shared_ptr heap; -private: static void heap_delete(mi_heap_t* hp) { if (hp != NULL) { mi_heap_delete(hp); } } }; +// STL allocator allocation in a specific heap +template struct mi_heap_stl_allocator : public _mi_heap_stl_allocator_common { + using typename _mi_heap_stl_allocator_common::size_type; + + template struct rebind { typedef mi_heap_stl_allocator other; }; + + using _mi_heap_stl_allocator_common::_mi_heap_stl_allocator_common; + mi_heap_stl_allocator select_on_container_copy_construction() const { return *this; } + void deallocate(T* p, size_type) { mi_free(p); } + +protected: + template + friend struct mi_heap_stl_allocator; + template + friend bool operator==(const mi_heap_stl_allocator& first, const mi_heap_stl_allocator& second) mi_attr_noexcept; + template + friend bool operator!=(const mi_heap_stl_allocator& first, const mi_heap_stl_allocator& second) mi_attr_noexcept; +}; + template bool operator==(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (x.heap == y.heap); } template bool operator!=(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (x.heap != y.heap); } // STL allocator allocation in a specific heap, where `free` does nothing and // the heap is destroyed in one go on destruction -- use with care! -template struct mi_heap_destroy_stl_allocator { - typedef T value_type; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - typedef value_type& reference; - typedef value_type const& const_reference; - typedef value_type* pointer; - typedef value_type const* const_pointer; +template struct mi_heap_destroy_stl_allocator : public _mi_heap_stl_allocator_common { + using typename _mi_heap_stl_allocator_common::size_type; + template struct rebind { typedef mi_heap_destroy_stl_allocator other; }; - mi_heap_destroy_stl_allocator() { - mi_heap_t* hp = mi_heap_new(); - this->heap.reset(hp, heap_destroy); - } - mi_heap_destroy_stl_allocator(mi_heap_t* hp) : heap(hp) { } /* will not delete or destroy the passed-in heap; nor free any allocated objects it allocates in the heap! */ - mi_heap_destroy_stl_allocator(const mi_heap_destroy_stl_allocator& x) mi_attr_noexcept : heap(x.heap) { } - template mi_heap_destroy_stl_allocator(const mi_heap_destroy_stl_allocator& x) mi_attr_noexcept : heap(x.heap) { } - mi_heap_destroy_stl_allocator select_on_container_copy_construction() const { return *this; } + using _mi_heap_stl_allocator_common::_mi_heap_stl_allocator_common; + mi_heap_destroy_stl_allocator select_on_container_copy_construction() const { return *this; } void deallocate(T* p, size_type) { /* do nothing as we destroy the heap on destruct. */ } - #if (__cplusplus >= 201703L) // C++17 - mi_decl_nodiscard T* allocate(size_type count) { return static_cast(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); } - mi_decl_nodiscard T* allocate(size_type count, const void*) { return allocate(count); } - #else - mi_decl_nodiscard pointer allocate(size_type count, const void* = 0) { return static_cast(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(value_type))); } - #endif - - #if ((__cplusplus >= 201103L) || (_MSC_VER > 1900)) // C++11 - using propagate_on_container_copy_assignment = std::true_type; - using propagate_on_container_move_assignment = std::true_type; - using propagate_on_container_swap = std::true_type; - using is_always_equal = std::false_type; - template void construct(U* p, Args&& ...args) { ::new(p) U(std::forward(args)...); } - template void destroy(U* p) mi_attr_noexcept { p->~U(); } - #else - void construct(pointer p, value_type const& val) { ::new(p) value_type(val); } - void destroy(pointer p) { p->~value_type(); } - #endif - - size_type max_size() const mi_attr_noexcept { return (PTRDIFF_MAX / sizeof(value_type)); } - pointer address(reference x) const { return &x; } - const_pointer address(const_reference x) const { return &x; } - -// protected: - std::shared_ptr heap; - -private: - static void heap_destroy(mi_heap_t* hp) { if (hp != NULL) { mi_heap_destroy(hp); } } +protected: + template + friend struct mi_heap_destroy_stl_allocator; + template + friend bool operator==(const mi_heap_destroy_stl_allocator& first, const mi_heap_destroy_stl_allocator& second) mi_attr_noexcept; + template + friend bool operator!=(const mi_heap_destroy_stl_allocator& first, const mi_heap_destroy_stl_allocator& second) mi_attr_noexcept; }; template bool operator==(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (x.heap == y.heap); } From c1299484def0a5282dcdbe53da79454aafd949ba Mon Sep 17 00:00:00 2001 From: Daan Leijen Date: Sun, 27 Nov 2022 13:00:15 -0800 Subject: [PATCH 2/2] refactor C++ STL allocator definitions (pr #651) --- include/mimalloc.h | 60 ++++++++++++++++++++---------------------- test/main-override.cpp | 4 +-- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/include/mimalloc.h b/include/mimalloc.h index 8fd2eedd..d70d28ed 100644 --- a/include/mimalloc.h +++ b/include/mimalloc.h @@ -441,6 +441,8 @@ template struct _mi_stl_allocator_common { template struct mi_stl_allocator : public _mi_stl_allocator_common { using typename _mi_stl_allocator_common::size_type; + using typename _mi_stl_allocator_common::value_type; + using typename _mi_stl_allocator_common::pointer; template struct rebind { typedef mi_stl_allocator other; }; mi_stl_allocator() mi_attr_noexcept = default; @@ -464,19 +466,17 @@ template struct mi_stl_allocator : public _mi_stl_allocator_common { template bool operator==(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return true; } template bool operator!=(const mi_stl_allocator& , const mi_stl_allocator& ) mi_attr_noexcept { return false; } + #if (__cplusplus >= 201103L) || (_MSC_VER > 1900) // C++11 #include // std::shared_ptr +// Common base class for STL allocators in a specific heap template struct _mi_heap_stl_allocator_common : public _mi_stl_allocator_common { using typename _mi_stl_allocator_common::size_type; + using typename _mi_stl_allocator_common::value_type; + using typename _mi_stl_allocator_common::pointer; - _mi_heap_stl_allocator_common() { - mi_heap_t* hp = mi_heap_new(); - this->heap.reset(hp, heap_delete); - } _mi_heap_stl_allocator_common(mi_heap_t* hp) : heap(hp) { } /* will not delete or destroy the passed in heap */ - _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } - template _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } #if (__cplusplus >= 201703L) // C++17 mi_decl_nodiscard T* allocate(size_type count) { return static_cast(mi_heap_alloc_new_n(this->heap.get(), count, sizeof(T))); } @@ -490,58 +490,54 @@ template struct _mi_heap_stl_allocator_common : public _mi_stl_allocato #endif void collect(bool force) { mi_heap_collect(this->heap.get(), force); } + template bool is_equal(const _mi_heap_stl_allocator_common& x) { return (this->heap == x.heap); } protected: std::shared_ptr heap; + template friend struct _mi_heap_stl_allocator_common; + + _mi_heap_stl_allocator_common(bool destroy) { + mi_heap_t* hp = mi_heap_new(); + this->heap.reset(hp, (destroy ? &heap_destroy : &heap_delete)); /* calls heap_delete/destroy when the refcount drops to zero */ + } + _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } + template _mi_heap_stl_allocator_common(const _mi_heap_stl_allocator_common& x) mi_attr_noexcept : heap(x.heap) { } +private: static void heap_delete(mi_heap_t* hp) { if (hp != NULL) { mi_heap_delete(hp); } } + static void heap_destroy(mi_heap_t* hp) { if (hp != NULL) { mi_heap_destroy(hp); } } }; // STL allocator allocation in a specific heap template struct mi_heap_stl_allocator : public _mi_heap_stl_allocator_common { using typename _mi_heap_stl_allocator_common::size_type; + mi_heap_stl_allocator() : _mi_heap_stl_allocator_common(false) { } /* delete on destruction */ + mi_heap_stl_allocator(mi_heap_t* hp) : _mi_heap_stl_allocator_common(hp) { } /* no delete or destroy on the passed in heap */ + template mi_heap_stl_allocator(const mi_heap_stl_allocator& x) mi_attr_noexcept : _mi_heap_stl_allocator_common(x) { } - template struct rebind { typedef mi_heap_stl_allocator other; }; - - using _mi_heap_stl_allocator_common::_mi_heap_stl_allocator_common; mi_heap_stl_allocator select_on_container_copy_construction() const { return *this; } void deallocate(T* p, size_type) { mi_free(p); } - -protected: - template - friend struct mi_heap_stl_allocator; - template - friend bool operator==(const mi_heap_stl_allocator& first, const mi_heap_stl_allocator& second) mi_attr_noexcept; - template - friend bool operator!=(const mi_heap_stl_allocator& first, const mi_heap_stl_allocator& second) mi_attr_noexcept; + template struct rebind { typedef mi_heap_stl_allocator other; }; }; -template bool operator==(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (x.heap == y.heap); } -template bool operator!=(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (x.heap != y.heap); } +template bool operator==(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (x.is_equal(y)); } +template bool operator!=(const mi_heap_stl_allocator& x, const mi_heap_stl_allocator& y) mi_attr_noexcept { return (!x.is_equal(y)); } // STL allocator allocation in a specific heap, where `free` does nothing and // the heap is destroyed in one go on destruction -- use with care! template struct mi_heap_destroy_stl_allocator : public _mi_heap_stl_allocator_common { using typename _mi_heap_stl_allocator_common::size_type; + mi_heap_destroy_stl_allocator() : _mi_heap_stl_allocator_common(true) { } /* destroy on destruction */ + template mi_heap_destroy_stl_allocator(const mi_heap_destroy_stl_allocator& x) mi_attr_noexcept : _mi_heap_stl_allocator_common(x) { } - template struct rebind { typedef mi_heap_destroy_stl_allocator other; }; - - using _mi_heap_stl_allocator_common::_mi_heap_stl_allocator_common; mi_heap_destroy_stl_allocator select_on_container_copy_construction() const { return *this; } void deallocate(T* p, size_type) { /* do nothing as we destroy the heap on destruct. */ } - -protected: - template - friend struct mi_heap_destroy_stl_allocator; - template - friend bool operator==(const mi_heap_destroy_stl_allocator& first, const mi_heap_destroy_stl_allocator& second) mi_attr_noexcept; - template - friend bool operator!=(const mi_heap_destroy_stl_allocator& first, const mi_heap_destroy_stl_allocator& second) mi_attr_noexcept; + template struct rebind { typedef mi_heap_destroy_stl_allocator other; }; }; -template bool operator==(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (x.heap == y.heap); } -template bool operator!=(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (x.heap != y.heap); } +template bool operator==(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (x.is_equal(y)); } +template bool operator!=(const mi_heap_destroy_stl_allocator& x, const mi_heap_destroy_stl_allocator& y) mi_attr_noexcept { return (!x.is_equal(y)); } #endif // C++11 diff --git a/test/main-override.cpp b/test/main-override.cpp index 81f57298..63bf20d8 100644 --- a/test/main-override.cpp +++ b/test/main-override.cpp @@ -181,7 +181,7 @@ static void strdup_test() { // Issue #202 static void heap_no_delete_worker() { mi_heap_t* heap = mi_heap_new(); - void* q = mi_heap_malloc(heap, 1024); + void* q = mi_heap_malloc(heap, 1024); (void)(q); // mi_heap_delete(heap); // uncomment to prevent assertion } @@ -245,7 +245,7 @@ static void heap_thread_free_huge_worker() { static void heap_thread_free_huge() { for (int i = 0; i < 10; i++) { shared_p = mi_malloc(1024 * 1024 * 1024); - auto t1 = std::thread(heap_thread_free_large_worker); + auto t1 = std::thread(heap_thread_free_huge_worker); t1.join(); } }