diff options
Diffstat (limited to 'src/nxt_mem_pool.c')
-rw-r--r-- | src/nxt_mem_pool.c | 641 |
1 files changed, 641 insertions, 0 deletions
diff --git a/src/nxt_mem_pool.c b/src/nxt_mem_pool.c new file mode 100644 index 00000000..0d4b5688 --- /dev/null +++ b/src/nxt_mem_pool.c @@ -0,0 +1,641 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_main.h> + + +/* + * The pool allocator provides cheap allocation of small objects. + * The objects are allocated from larger preallocated chunks. + * + * aligned and non-aligned allocations, + * cache of reusable objects, lvlhsh-specific cache + * eliminating align padding + * data locality + * freeing on pool destruction + * freeing large allocations + */ + + +static void *nxt_mem_pool_align(nxt_mem_pool_t *mp, size_t alignment, + size_t size); +static void *nxt_mem_pool_ext(nxt_mem_pool_t *mp, size_t size); +static nxt_mem_pool_chunk_t *nxt_mem_pool_next_chunk(nxt_mem_pool_t *mp, + nxt_mem_pool_chunk_t *chunk); +static nxt_mem_pool_chunk_t *nxt_mem_pool_chunk(nxt_mem_pool_t *mp); +static void *nxt_mem_lvlhsh_alloc_chunk(nxt_mem_pool_cache_t *cache, + size_t size, nxt_uint_t nalloc); + + +#if (NXT_DEBUG) + +static nxt_bool_t +nxt_mem_pool_thread_is_invalid(nxt_mem_pool_t *mp) +{ + nxt_tid_t tid; + nxt_thread_t *thr; + + thr = nxt_thread(); + tid = nxt_thread_tid(thr); + + if (nxt_slow_path(mp->tid != tid)) { + + if (mp->pid == nxt_pid) { + nxt_log_alert(thr->log, "mem_pool locked by thread %PT", mp->tid); + nxt_abort(); + return 1; + } + + mp->pid = nxt_pid; + mp->tid = tid; + } + + return 0; +} + + +/* SunC does not support C99 variadic macro with empty __VA_ARGS__. */ + +#define \ +nxt_mem_pool_thread_assert(mp) \ + if (nxt_mem_pool_thread_is_invalid(mp)) \ + return + + +#define \ +nxt_mem_pool_thread_assert_return(mp, ret) \ + if (nxt_mem_pool_thread_is_invalid(mp)) \ + return ret + + +#else /* !(NXT_DEBUG) */ + +#define \ +nxt_mem_pool_thread_assert(mp) + +#define \ +nxt_mem_pool_thread_assert_return(mp, ret) + +#endif + + +nxt_mem_pool_t * +nxt_mem_pool_create(size_t size) +{ + u_char *p; + size_t min_ext_size; + nxt_mem_pool_t *mp; + + mp = nxt_malloc(size); + + if (nxt_fast_path(mp != NULL)) { + + mp->chunk_size = (uint32_t) size; + + min_ext_size = size - sizeof(nxt_mem_pool_t) + 1; + mp->min_ext_size = (uint32_t) nxt_min(min_ext_size, + NXT_MEM_POOL_MIN_EXT_SIZE); + + nxt_malloc_usable_size(mp, size); + + p = (u_char *) mp; + + mp->chunk.free = p + sizeof(nxt_mem_pool_t); + mp->chunk.end = p + size; + mp->chunk.next = NULL; + mp->chunk.fails = 0; + + mp->current = &mp->chunk; + mp->ext = NULL; + mp->cleanup = NULL; + mp->cache = NULL; + + nxt_thread_log_debug("mem pool chunk size:%uz avail:%uz", + size, mp->chunk.end - mp->chunk.free); + + nxt_mem_pool_debug_lock(mp, nxt_thread_tid(NULL)); + } + + return mp; +} + + +void +nxt_mem_pool_destroy(nxt_mem_pool_t *mp) +{ + nxt_mem_pool_ext_t *ext; + nxt_mem_pool_chunk_t *chunk, *next; + nxt_mem_pool_cleanup_t *mpcl; + + nxt_mem_pool_thread_assert(mp); + + for (mpcl = mp->cleanup; mpcl != NULL; mpcl = mpcl->next) { + if (mpcl->handler != NULL) { + nxt_thread_log_debug("mem pool cleanup: %p", mpcl); + mpcl->handler(mpcl->data); + } + } + + for (ext = mp->ext; ext != NULL; ext = ext->next) { + if (ext->data != NULL) { + nxt_free(ext->data); + } + } + + chunk = &mp->chunk; + + do { + nxt_thread_log_debug("mem pool chunk fails:%uD unused:%uz", + chunk->fails, chunk->end - chunk->free); + next = chunk->next; + nxt_free(chunk); + chunk = next; + + } while (chunk != NULL); +} + + +void * +nxt_mem_align(nxt_mem_pool_t *mp, size_t alignment, size_t size) +{ + nxt_mem_pool_thread_assert_return(mp, NULL); + + if (nxt_fast_path(size < mp->min_ext_size)) { + return nxt_mem_pool_align(mp, alignment, size); + } + + return nxt_mem_pool_ext(mp, size); +} + + +void * +nxt_mem_zalign(nxt_mem_pool_t *mp, size_t alignment, size_t size) +{ + void *p; + + p = nxt_mem_align(mp, alignment, size); + + if (nxt_fast_path(p != NULL)) { + nxt_memzero(p, size); + } + + return p; +} + + +/* + * Zero-filled aligned allocation, suitable for struct + * allocation without long double and SIMD values. + */ + +void * +nxt_mem_zalloc(nxt_mem_pool_t *mp, size_t size) +{ + void *p; + + p = nxt_mem_alloc(mp, size); + + if (nxt_fast_path(p != NULL)) { + nxt_memzero(p, size); + } + + return p; +} + + +void * +nxt_mem_buf(nxt_mem_pool_t *mp, size_t *sizep, nxt_uint_t flags) +{ + u_char *p; + size_t size; + + nxt_mem_pool_thread_assert_return(mp, NULL); + + size = *sizep; + + if (nxt_fast_path(size >= mp->min_ext_size)) { + + nxt_malloc_cutback(flags & NXT_MEM_BUF_CUTBACK, size); + + /* Windows only: try to minimize number of allocated pages. */ + p = nxt_mem_pool_ext(mp, size); + if (p != NULL) { + + if (flags & NXT_MEM_BUF_USABLE) { + nxt_malloc_usable_size(p, size); + } + + *sizep = size; + } + + return p; + } + + return nxt_mem_pool_align(mp, NXT_ALIGNMENT, size); +} + + +/* Non-aligned allocation, suitable for string allocation. */ + +void * +nxt_mem_nalloc(nxt_mem_pool_t *mp, size_t size) +{ + u_char *p; + nxt_mem_pool_chunk_t *chunk; + + nxt_mem_pool_thread_assert_return(mp, NULL); + + if (nxt_slow_path(size >= mp->min_ext_size)) { + return nxt_mem_pool_ext(mp, size); + } + + chunk = mp->current; + + for ( ;; ) { + p = chunk->end - size; + + if (nxt_fast_path(p >= chunk->free)) { + chunk->end = p; + return p; + } + + chunk = nxt_mem_pool_next_chunk(mp, chunk); + + if (nxt_slow_path(chunk == NULL)) { + return NULL; + } + } +} + + +/* An attempt to deallocate a large allocation outside pool. */ + +nxt_int_t +nxt_mem_free(nxt_mem_pool_t *mp, void *p) +{ + nxt_mem_pool_ext_t *ext; + + nxt_mem_pool_thread_assert_return(mp, NXT_DECLINED); + + for (ext = mp->ext; ext != NULL; ext = ext->next) { + + if (p == ext->data) { + nxt_free(ext->data); + ext->data = NULL; + + return NXT_OK; + } + } + + return NXT_DECLINED; +} + + +static void * +nxt_mem_pool_ext(nxt_mem_pool_t *mp, size_t size) +{ + void *p; + nxt_mem_pool_ext_t *ext; + + ext = nxt_mem_pool_align(mp, sizeof(void *), sizeof(nxt_mem_pool_ext_t)); + + if (nxt_fast_path(ext != NULL)) { + p = nxt_malloc(size); + + if (nxt_fast_path(p != NULL)) { + ext->data = p; + ext->next = mp->ext; + mp->ext = ext; + + return p; + } + } + + return NULL; +} + + +static void * +nxt_mem_pool_align(nxt_mem_pool_t *mp, size_t alignment, size_t size) +{ + u_char *p, *f; + nxt_mem_pool_chunk_t *chunk; + + chunk = mp->current; + + for ( ;; ) { + + p = nxt_align_ptr(chunk->free, alignment); + f = p + size; + + if (nxt_fast_path(f <= chunk->end)) { + chunk->free = f; + return p; + } + + chunk = nxt_mem_pool_next_chunk(mp, chunk); + + if (nxt_slow_path(chunk == NULL)) { + return NULL; + } + } +} + + +static nxt_mem_pool_chunk_t * +nxt_mem_pool_next_chunk(nxt_mem_pool_t *mp, nxt_mem_pool_chunk_t *chunk) +{ + nxt_bool_t full; + + full = (chunk->free == chunk->end || chunk->fails++ > 10); + + chunk = chunk->next; + + if (chunk == NULL) { + chunk = nxt_mem_pool_chunk(mp); + + if (nxt_slow_path(chunk == NULL)) { + return NULL; + } + } + + if (full) { + mp->current = chunk; + } + + return chunk; +} + + +static nxt_mem_pool_chunk_t * +nxt_mem_pool_chunk(nxt_mem_pool_t *mp) +{ + u_char *p; + size_t size; + nxt_mem_pool_chunk_t *ch, *chunk; + + size = mp->chunk_size; + + chunk = nxt_malloc(size); + + if (nxt_fast_path(chunk != NULL)) { + + nxt_malloc_usable_size(chunk, size); + + p = (u_char *) chunk; + + chunk->free = p + sizeof(nxt_mem_pool_chunk_t); + chunk->end = p + size; + chunk->next = NULL; + chunk->fails = 0; + + for (ch = mp->current; ch->next; ch = ch->next) { /* void */ } + + ch->next = chunk; + } + + return chunk; +} + + +nxt_mem_pool_cleanup_t * +nxt_mem_pool_cleanup(nxt_mem_pool_t *mp, size_t size) +{ + nxt_mem_pool_cleanup_t *mpcl; + + nxt_mem_pool_thread_assert_return(mp, NULL); + + mpcl = nxt_mem_pool_align(mp, sizeof(void *), + sizeof(nxt_mem_pool_cleanup_t)); + if (nxt_fast_path(mpcl != NULL)) { + + mpcl->handler = NULL; + mpcl->data = NULL; + + if (size != 0) { + mpcl->data = nxt_mem_alloc(mp, size); + if (nxt_slow_path(mpcl->data == NULL)) { + return NULL; + } + } + + mpcl->next = mp->cleanup; + mp->cleanup = mpcl; + + nxt_thread_log_debug("mem pool cleanup add: %p", mpcl); + } + + return mpcl; +} + + +/* Allocation of reusable object with specified size. */ + +void * +nxt_mem_cache_alloc0(nxt_mem_pool_t *mp, size_t size) +{ + void **pp; + nxt_mem_pool_cache_t *cache; + + nxt_mem_pool_thread_assert_return(mp, NULL); + + for (cache = mp->cache; cache != NULL; cache = cache->next) { + + if (cache->size == size && cache->nalloc == 0) { + + if (cache->free != NULL) { + pp = cache->free; + cache->free = *pp; + return pp; + } + + break; + } + } + + return nxt_mem_alloc(mp, size); +} + + +void * +nxt_mem_cache_zalloc0(nxt_mem_pool_t *mp, size_t size) +{ + void *p; + + p = nxt_mem_cache_alloc0(mp, size); + + if (nxt_fast_path(p != NULL)) { + nxt_memzero(p, size); + } + + return p; +} + + +/* Deallocation of reusable object with specified size. */ + +void +nxt_mem_cache_free0(nxt_mem_pool_t *mp, void *p, size_t size) +{ + void **pp; + nxt_mem_pool_cache_t *cache, **pcache; + + nxt_mem_pool_thread_assert(mp); + + pp = p; + + pcache = &mp->cache; + for (cache = mp->cache; cache != NULL; cache = cache->next) { + + if (cache->size == size && cache->nalloc == 0) { + *pp = cache->free; + cache->free = p; + return; + } + + pcache = &cache->next; + } + + /* Non-lvlhash caches are created only on return. */ + + cache = nxt_mem_pool_align(mp, sizeof(void *), + sizeof(nxt_mem_pool_cache_t)); + if (nxt_fast_path(cache != NULL)) { + *pp = NULL; + cache->size = (uint32_t) size; + cache->nalloc = 0; + cache->free = p; + cache->next = NULL; + *pcache = cache; + } +} + + +/* + * lvlhsh requires allocations aligned to a size of the allocations. + * This is not issue for slab-like allocators, but glibc allocator may + * waste memory on such aligned allocations. So nxt_mem_lvlhsh_alloc() + * allocates memory in chunks specified by the "nalloc" parameter + * except the first allocation. The first lvlhsh allocation is a bucket + * allocation and it is enough for small hashes and for early stage + * of a hash. By default lvlhsh uses 128-bytes buckets and levels. + * This allows to search up to 10 entries in one memory access and + * up to 160 entries in two memory accesses on 64-bit platform. + * And on 32-bit platform up to 15 entries and up to 480 entries + * respectively. + * + * After the bucket will be filled up with 10 64-bit entries or 15 + * 32-bit entries, lvlhsh will expand it to a level and content + * of the first bucket will spread to the level's new buckets. + * Number of the new buckets may be up to 11 on 64-bit or 16 on 32-bit + * platforms. It's better to allocate them together to eliminate + * wasting memory and CPU time. + * + * The "nalloc" should be 16 if bucket size is 128 bytes. + */ + + +/* Allocation of lvlhsh level or bucket with specified size. */ + +void * +nxt_mem_lvlhsh_alloc(void *ctx, size_t size, nxt_uint_t nalloc) +{ + void *p, **pp; + nxt_mem_pool_t *mp; + nxt_mem_pool_cache_t *cache; + + mp = ctx; + + nxt_mem_pool_thread_assert_return(mp, NULL); + + for (cache = mp->cache; cache != NULL; cache = cache->next) { + + if (cache->size == size && cache->nalloc != 0) { + + if (cache->free != NULL) { + pp = cache->free; + cache->free = *pp; + return pp; + } + + return nxt_mem_lvlhsh_alloc_chunk(cache, size, nalloc); + } + } + + cache = nxt_mem_pool_align(mp, sizeof(void *), + sizeof(nxt_mem_pool_cache_t)); + if (nxt_fast_path(cache != NULL)) { + + p = nxt_memalign(size, size); + + if (nxt_fast_path(p != NULL)) { + cache->size = (uint32_t) size; + cache->nalloc = nalloc; + cache->free = NULL; + cache->next = mp->cache; + mp->cache = cache; + return p; + } + } + + return NULL; +} + + +static void * +nxt_mem_lvlhsh_alloc_chunk(nxt_mem_pool_cache_t *cache, size_t size, + nxt_uint_t nalloc) +{ + char *m, *p, *end; + void **pp; + size_t n; + + n = (nalloc == 0) ? 1 : nalloc; + n *= size; + + m = nxt_memalign(size, n); + + if (nxt_fast_path(m != NULL)) { + + pp = &cache->free; + end = m + n; + + for (p = m + size; p < end; p = p + size) { + *pp = p; + pp = (void **) p; + } + + *pp = NULL; + } + + return m; +} + + +/* Deallocation of lvlhsh level or bucket with specified size. */ + +void +nxt_mem_lvlhsh_free(void *ctx, void *p, size_t size) +{ + void **pp; + nxt_mem_pool_t *mp; + nxt_mem_pool_cache_t *cache; + + mp = ctx; + + nxt_mem_pool_thread_assert(mp); + + pp = p; + + for (cache = mp->cache; cache != NULL; cache = cache->next) { + + if (cache->size == size && cache->nalloc != 0) { + *pp = cache->free; + cache->free = p; + return; + } + } +} |