From 280c2ae25c58948f7e4bf33c797177b1fbd84c06 Mon Sep 17 00:00:00 2001 From: Matthew Parkinson Date: Mon, 15 Jul 2019 17:49:40 +0100 Subject: [PATCH] Refactor ThreadAlloc Made common code between the Libc and C++ based releasing of allocators part of a parent class, which each implementation subclasses. --- src/ds/helpers.h | 13 +++ src/mem/threadalloc.h | 225 +++++++++++++++++++----------------------- 2 files changed, 113 insertions(+), 125 deletions(-) diff --git a/src/ds/helpers.h b/src/ds/helpers.h index fdae63f..7fe6633 100644 --- a/src/ds/helpers.h +++ b/src/ds/helpers.h @@ -87,4 +87,17 @@ namespace snmalloc return array[i & (rlength - 1)]; } }; + + /** + * Helper class to execute a specified function on destruction. + */ + template + class OnDestruct + { + public: + ~OnDestruct() + { + f(); + } + }; } // namespace snmalloc diff --git a/src/mem/threadalloc.h b/src/mem/threadalloc.h index 922c8f6..56383e6 100644 --- a/src/mem/threadalloc.h +++ b/src/mem/threadalloc.h @@ -9,8 +9,6 @@ namespace snmalloc { - extern "C" void _malloc_thread_cleanup(void); - /** * A global fake allocator object. This never allocates memory and, as a * result, never owns any slabs. On the slow paths, where it would fetch @@ -33,15 +31,106 @@ namespace snmalloc */ class ThreadAllocUntypedWrapper { + protected: + static void register_cleanup() {} + public: - static SNMALLOC_FAST_PATH Alloc*& get() + static SNMALLOC_FAST_PATH Alloc* get_noncachable() + { + return (Alloc*&)ThreadAllocUntyped::get(); + } + + static SNMALLOC_FAST_PATH Alloc* get() { return (Alloc*&)ThreadAllocUntyped::get(); } - static void register_cleanup() {} }; #endif + /** + * Common aspects of thread local allocator. Subclasses handle how releasing + * the allocator is triggered. + */ + class ThreadAllocCommon + { + friend void* lazy_replacement_slow(); + + protected: + static inline void inner_release() + { + auto& per_thread = get_reference(); + if (per_thread != &GlobalPlaceHolder) + { + current_alloc_pool()->release(per_thread); + per_thread = &GlobalPlaceHolder; + } + } + + /** + * Default clean up does nothing except print statistics if enabled. + **/ + static void register_cleanup() + { +#ifdef USE_SNMALLOC_STATS + Singleton::get(); +#endif + } + +#ifdef USE_SNMALLOC_STATS + static void print_stats() + { + Stats s; + current_alloc_pool()->aggregate_stats(s); + s.print(std::cout); + } + + static int atexit_print_stats() noexcept + { + return atexit(print_stats); + } +#endif + + static inline Alloc*& get_reference() + { + static thread_local Alloc* alloc = &GlobalPlaceHolder; + return alloc; + } + + public: + /** + * Public interface, returns the allocator for this thread, constructing + * one if necessary. + * + * If no operations have been performed on an allocator returned by either + * `get()` nor `get_noncachable()`, then the value contained in the return + * will be an Alloc* that will always use the slow path. + * + * Only use this API if you intend to use the returned allocator just once + * per call, or if you know other calls have already been made to the + * allocator. + */ + static inline Alloc* get_noncachable() + { + return get_reference(); + } + + /** + * Public interface, returns the allocator for this thread, constructing + * one if necessary. + * + * The returned Alloc* is guaranteed to be initialised. This incurs a cost, + * so use `get_noncachable` if you can meet its criteria. + */ + static SNMALLOC_FAST_PATH Alloc* get() + { + auto alloc = get_reference(); + auto new_alloc = lazy_replacement(alloc); + return (likely(new_alloc == nullptr)) ? + alloc : + reinterpret_cast(new_alloc); + } + }; + /** * Version of the `ThreadAlloc` interface that uses a hook provided by libc * to destroy thread-local state. This is the ideal option, because it @@ -51,7 +140,7 @@ namespace snmalloc * This class is used only when snmalloc is compiled as part of a compatible * libc (for example, FreeBSD libc). */ - class ThreadAllocLibcCleanup + class ThreadAllocLibcCleanup : public ThreadAllocCommon { /** * Libc will call `_malloc_thread_cleanup` just before a thread terminates. @@ -59,50 +148,6 @@ namespace snmalloc * the state. */ friend void _malloc_thread_cleanup(void); - - /** - * Function called when the thread exits. This is guaranteed to be called - * precisely once per thread and releases the current allocator. - */ - static inline void exit() - { - auto* per_thread = get(); - if ((per_thread != &GlobalPlaceHolder) && (per_thread != nullptr)) - { - current_alloc_pool()->release(per_thread); - per_thread = nullptr; - } - } - - public: - /** - * Returns a pointer to the allocator associated with this thread. If - * `create` is true, it will create an allocator if one does not exist, - * otherwise it will return `nullptr` in this case. This should be called - * with `create == false` only during thread teardown. - * - * The non-create case exists so that the `per_thread` variable can be a - * local static and not a global, allowing ODR to deduplicate it. - */ - static SNMALLOC_FAST_PATH Alloc*& get() - { - static thread_local Alloc* per_thread = &GlobalPlaceHolder; - return per_thread; - } - static void register_cleanup() {} - }; - - /** - * Helper class to execute a specified function on destruction. - */ - template - class OnDestruct - { - public: - ~OnDestruct() - { - f(); - } }; /** @@ -117,82 +162,17 @@ namespace snmalloc * environment and so should be the simplest for initial bringup on an * unsupported platform. It is currently used in the FreeBSD kernel version. */ - class ThreadAllocThreadDestructor + class ThreadAllocThreadDestructor : public ThreadAllocCommon { template friend class OnDestruct; - /** - * Releases the allocator owned by this thread. - */ - static void inner_release() - { - Alloc*& a = get_noncachable(); - if (a != &GlobalPlaceHolder) - { - current_alloc_pool()->release(a); - a = &GlobalPlaceHolder; - } - } - -#ifdef USE_SNMALLOC_STATS - static void print_stats() - { - Stats s; - current_alloc_pool()->aggregate_stats(s); - s.print(std::cout); - } - - static int atexit_print_stats() - { - return atexit(print_stats); - } -#endif - public: - /** - * Public interface, returns the allocator for this thread, constructing - * one if necessary. - * - * If no operations have been performed on an allocator returned by either - * `get()` nor `get_noncachable`, then the value contained in the return - * will be an Alloc* that will always use the slow path. The Alloc*& will - * be updated, and is performant to use for subsequent calls. - * - * Only use this API if you intend to use the returned allocator just once - * per call, you store the indirection (Alloc*&) rather than (Alloc*), or if - * you know other calls have already been made to the allocator. - */ - static inline Alloc*& get_noncachable() - { - static thread_local Alloc* alloc = &GlobalPlaceHolder; - return alloc; - } - - /** - * Public interface, returns the allocator for this thread, constructing - * one if necessary. - * - * The returned Alloc* is guaranteed to be initialised. This incurs a cost, - * so use `get_noncachable` if you can meet its criteria. - */ - static SNMALLOC_FAST_PATH Alloc* get() - { - auto alloc = get_noncachable(); - auto new_alloc = lazy_replacement(alloc); - return (likely(new_alloc == nullptr)) ? - alloc : - reinterpret_cast(new_alloc); - } - static void register_cleanup() { - static thread_local OnDestruct - tidier; + static thread_local OnDestruct tidier; -#ifdef USE_SNMALLOC_STATS - Singleton::get(); -#endif + ThreadAllocCommon::register_cleanup(); } }; @@ -203,11 +183,9 @@ namespace snmalloc */ extern "C" void _malloc_thread_cleanup(void) { - ThreadAllocLibcCleanup::exit(); + ThreadAllocLibcCleanup::inner_release(); } using ThreadAlloc = ThreadAllocLibcCleanup; -#elif defined(SNMALLOC_USE_THREAD_DESTRUCTOR) - using ThreadAlloc = ThreadAllocThreadDestructor; #elif defined(SNMALLOC_EXTERNAL_THREAD_ALLOC) using ThreadAlloc = ThreadAllocUntypedWrapper; #else @@ -221,11 +199,8 @@ namespace snmalloc */ SNMALLOC_SLOW_PATH inline void* lazy_replacement_slow() { - auto*& local_alloc = ThreadAlloc::get_noncachable(); - if ((local_alloc != nullptr) && (local_alloc != &GlobalPlaceHolder)) - { - return local_alloc; - } + auto*& local_alloc = ThreadAlloc::get_reference(); + assert(local_alloc == &GlobalPlaceHolder); local_alloc = current_alloc_pool()->acquire(); assert(local_alloc != &GlobalPlaceHolder); ThreadAlloc::register_cleanup();