Refactor ThreadAlloc
Made common code between the Libc and C++ based releasing of allocators part of a parent class, which each implementation subclasses.
This commit is contained in:
@@ -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<int, atexit_print_stats>::get();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
static void print_stats()
|
||||
{
|
||||
Stats s;
|
||||
current_alloc_pool()->aggregate_stats(s);
|
||||
s.print<Alloc>(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<Alloc*>(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<void f()>
|
||||
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<void f()>
|
||||
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<Alloc>(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<Alloc*>(new_alloc);
|
||||
}
|
||||
|
||||
static void register_cleanup()
|
||||
{
|
||||
static thread_local OnDestruct<ThreadAllocThreadDestructor::inner_release>
|
||||
tidier;
|
||||
static thread_local OnDestruct<ThreadAllocCommon::inner_release> tidier;
|
||||
|
||||
#ifdef USE_SNMALLOC_STATS
|
||||
Singleton<int, atexit_print_stats>::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();
|
||||
|
||||
Reference in New Issue
Block a user