summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libdimension/pool.c115
-rw-r--r--libdimension/tests/pool.c4
2 files changed, 84 insertions, 35 deletions
diff --git a/libdimension/pool.c b/libdimension/pool.c
index 82cd46f..a7d6b4f 100644
--- a/libdimension/pool.c
+++ b/libdimension/pool.c
@@ -26,51 +26,63 @@
#include "dimension-internal.h"
#include <stdatomic.h>
-/// A single allocation and associated destructor.
-typedef struct dmnsn_allocation {
- void *ptr;
- dmnsn_callback_fn *cleanup_fn;
-} dmnsn_allocation;
-
/// Number of pointers per block, we want a block to fit in a single page.
-#define DMNSN_POOL_BLOCK_SIZE ((4096 - 4*sizeof(void *))/sizeof(dmnsn_allocation))
+#define DMNSN_POOL_BLOCK_SIZE (4096/sizeof(void *) - 4)
+/// Number of pointers per tidy block
+#define DMNSN_TIDY_BLOCK_SIZE ((4096 - 4*sizeof(void *))/(sizeof(void *) + sizeof(dmnsn_callback_fn *)))
-/// A single block in a thread pool.
+/// A single block in a thread-specific pool.
typedef struct dmnsn_pool_block {
/// Current index into allocs[].
size_t i;
/// All allocations in the current block.
- dmnsn_allocation allocs[DMNSN_POOL_BLOCK_SIZE];
+ void *allocs[DMNSN_POOL_BLOCK_SIZE];
/// Tail pointer to the previous block in the global chain.
struct dmnsn_pool_block *prev;
} dmnsn_pool_block;
+/// A single tidy block in a thread-specific pool.
+typedef struct dmnsn_tidy_block {
+ /// Current index into allocs[].
+ size_t i;
+ /// All allocations in the current block.
+ void *allocs[DMNSN_TIDY_BLOCK_SIZE];
+ /// All cleanup callbacks in the current block.
+ dmnsn_callback_fn *cleanup_fns[DMNSN_TIDY_BLOCK_SIZE];
+ /// Tail pointer to the previous tidy block in the global chain.
+ struct dmnsn_tidy_block *prev;
+} dmnsn_tidy_block;
+
/// dmnsn_pool implementation.
struct dmnsn_pool {
- /// Thread-local block.
+ /// Thread-local regular block.
pthread_key_t thread_block;
- /// Global chain of pools.
+ /// Thread-local tidy block.
+ pthread_key_t thread_tidy_block;
+
+ /// Global chain of regular blocks.
atomic(dmnsn_pool_block *) chain;
+ /// Global chain of tidy blocks.
+ atomic(dmnsn_tidy_block *) tidy_chain;
};
dmnsn_pool *
dmnsn_new_pool(void)
{
dmnsn_pool *pool = DMNSN_MALLOC(dmnsn_pool);
+
dmnsn_key_create(&pool->thread_block, NULL);
+ dmnsn_key_create(&pool->thread_tidy_block, NULL);
+
atomic_store_explicit(&pool->chain, NULL, memory_order_relaxed);
+ atomic_store_explicit(&pool->tidy_chain, NULL, memory_order_relaxed);
+
return pool;
}
void *
dmnsn_palloc(dmnsn_pool *pool, size_t size)
{
- return dmnsn_palloc_tidy(pool, size, NULL);
-}
-
-void *
-dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn)
-{
dmnsn_pool_block *old_block = pthread_getspecific(pool->thread_block);
dmnsn_pool_block *new_block = old_block;
@@ -79,20 +91,46 @@ dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn)
new_block->i = 0;
}
- dmnsn_allocation *alloc = new_block->allocs + new_block->i;
- void *result = alloc->ptr = dmnsn_malloc(size);
- alloc->cleanup_fn = cleanup_fn;
- ++new_block->i;
+ void *result = dmnsn_malloc(size);
+ new_block->allocs[new_block->i++] = result;
if (dmnsn_unlikely(new_block != old_block)) {
dmnsn_setspecific(pool->thread_block, new_block);
// Atomically update pool->chain
- dmnsn_pool_block *chain;
- do {
- chain = atomic_load(&pool->chain);
- } while (!atomic_compare_exchange_weak(&pool->chain, &chain, new_block));
- new_block->prev = chain;
+ dmnsn_pool_block *old_chain = atomic_exchange(&pool->chain, new_block);
+ new_block->prev = old_chain;
+ }
+
+ return result;
+}
+
+void *
+dmnsn_palloc_tidy(dmnsn_pool *pool, size_t size, dmnsn_callback_fn *cleanup_fn)
+{
+ dmnsn_assert(cleanup_fn != NULL, "NULL cleanup_fn");
+
+ dmnsn_tidy_block *old_block = pthread_getspecific(pool->thread_tidy_block);
+
+ dmnsn_tidy_block *new_block = old_block;
+ if (dmnsn_unlikely(!old_block || old_block->i == DMNSN_TIDY_BLOCK_SIZE)) {
+ new_block = DMNSN_MALLOC(dmnsn_tidy_block);
+ new_block->i = 0;
+ }
+
+ void *result = dmnsn_malloc(size);
+
+ size_t i = new_block->i;
+ new_block->allocs[i] = result;
+ new_block->cleanup_fns[i] = cleanup_fn;
+ ++new_block->i;
+
+ if (dmnsn_unlikely(new_block != old_block)) {
+ dmnsn_setspecific(pool->thread_tidy_block, new_block);
+
+ // Atomically update pool->tidy_chain
+ dmnsn_tidy_block *old_chain = atomic_exchange(&pool->tidy_chain, new_block);
+ new_block->prev = old_chain;
}
return result;
@@ -107,13 +145,9 @@ dmnsn_delete_pool(dmnsn_pool *pool)
dmnsn_pool_block *block = atomic_load_explicit(&pool->chain, memory_order_relaxed);
while (block) {
- // Free all the allocations in reverse order
+ // Free all the allocations
for (size_t i = block->i; i-- > 0;) {
- dmnsn_allocation *alloc = block->allocs + i;
- if (alloc->cleanup_fn) {
- alloc->cleanup_fn(alloc->ptr);
- }
- dmnsn_free(alloc->ptr);
+ dmnsn_free(block->allocs[i]);
}
// Free the block itself and go to the previous one
@@ -122,6 +156,21 @@ dmnsn_delete_pool(dmnsn_pool *pool)
dmnsn_free(saved);
}
- dmnsn_key_delete(pool->thread_block);
+ dmnsn_tidy_block *tidy_block = atomic_load_explicit(&pool->tidy_chain, memory_order_relaxed);
+ while (tidy_block) {
+ // Free all the allocations
+ for (size_t i = tidy_block->i; i-- > 0;) {
+ void *ptr = tidy_block->allocs[i];
+ tidy_block->cleanup_fns[i](ptr);
+ dmnsn_free(ptr);
+ }
+
+ // Free the block itself and go to the previous one
+ dmnsn_tidy_block *saved = tidy_block;
+ tidy_block = tidy_block->prev;
+ dmnsn_free(saved);
+ }
+
+ dmnsn_key_delete(pool->thread_tidy_block);
dmnsn_free(pool);
}
diff --git a/libdimension/tests/pool.c b/libdimension/tests/pool.c
index 83f25f7..1722d66 100644
--- a/libdimension/tests/pool.c
+++ b/libdimension/tests/pool.c
@@ -59,10 +59,10 @@ callback(void *ptr)
DMNSN_TEST(pool, callback)
{
- DMNSN_PALLOC_TIDY(pool, int, NULL);
+ DMNSN_PALLOC(pool, int);
DMNSN_PALLOC_TIDY(pool, int, callback);
DMNSN_PALLOC_TIDY(pool, int, callback);
- DMNSN_PALLOC_TIDY(pool, int, NULL);
+ DMNSN_PALLOC(pool, int);
dmnsn_delete_pool(pool);
pool = NULL;