diff options
Diffstat (limited to 'src/nxt_mem_cache_pool.c')
-rw-r--r-- | src/nxt_mem_cache_pool.c | 767 |
1 files changed, 767 insertions, 0 deletions
diff --git a/src/nxt_mem_cache_pool.c b/src/nxt_mem_cache_pool.c new file mode 100644 index 00000000..e949dff9 --- /dev/null +++ b/src/nxt_mem_cache_pool.c @@ -0,0 +1,767 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include <nxt_main.h> + + +/* + * A memory cache pool allocates memory in clusters of specified size and + * aligned to page_alignment. A cluster is divided on pages of specified + * size. Page size must be a power of 2. A page can be used entirely or + * can be divided on chunks of equal size. Chunk size must be a power of 2. + * A cluster can contains pages with different chunk sizes. Cluster size + * must be multiple of page size and may be not a power of 2. Allocations + * greater than page are allocated outside clusters. Start addresses and + * sizes of clusters and large allocations are stored in rbtree to find + * them on free operations. The rbtree nodes are sorted by start addresses. + */ + + +typedef struct nxt_mem_cache_page_s nxt_mem_cache_page_t; + +struct nxt_mem_cache_page_s { + /* Chunk bitmap. There can be no more than 32 chunks in a page. */ + uint8_t map[4]; + + /* Number of free chunks of a chunked page. */ + uint8_t chunks; + + /* + * Size of chunks or page shifted by pool->chunk_size_shift. + * Zero means that page is free. + */ + uint8_t size; + + /* + * Page number in page cluster. + * There can be no more than 65536 pages in a cluster. + */ + uint16_t number; + + /* + * Used to link pages with free chunks in pool chunk slot list + * or to link free pages in clusters. + */ + nxt_queue_link_t link; +}; + + +typedef struct { + NXT_RBTREE_NODE (node); + uint8_t type; + uint32_t size; + + u_char *start; + nxt_mem_cache_page_t pages[]; +} nxt_mem_cache_block_t; + + +typedef struct { + nxt_queue_t pages; +#if (NXT_64BIT) + uint32_t size; + uint32_t chunks; +#else + uint16_t size; + uint16_t chunks; +#endif +} nxt_mem_cache_slot_t; + + +struct nxt_mem_cache_pool_s { + /* rbtree of nxt_mem_cache_block_t. */ + nxt_rbtree_t pages; + + nxt_queue_t free_pages; + + uint8_t chunk_size_shift; + uint8_t page_size_shift; + uint32_t page_size; + uint32_t page_alignment; + uint32_t cluster_size; + + nxt_mem_cache_slot_t slots[]; +}; + + +/* A cluster cache block. */ +#define NXT_MEM_CACHE_CLUSTER_BLOCK 0 + +/* A discrete cache block of large allocation. */ +#define NXT_MEM_CACHE_DISCRETE_BLOCK 1 +/* + * An embedded cache block allocated together with large allocation + * just after the allocation. + */ +#define NXT_MEM_CACHE_EMBEDDED_BLOCK 2 + + +#define \ +nxt_mem_cache_chunk_is_free(map, chunk) \ + ((map[chunk / 8] & (0x80 >> (chunk & 7))) == 0) + + +#define \ +nxt_mem_cache_chunk_set_free(map, chunk) \ + map[chunk / 8] &= ~(0x80 >> (chunk & 7)) + + +#define \ +nxt_mem_cache_free_junk(p, size) \ + nxt_memset((p), 0x5A, size) + + +static nxt_uint_t nxt_mem_cache_shift(nxt_uint_t n); +static void *nxt_mem_cache_alloc_small(nxt_mem_cache_pool_t *pool, size_t size); +static nxt_uint_t nxt_mem_cache_alloc_chunk(u_char *map, nxt_uint_t size); +static nxt_mem_cache_page_t * + nxt_mem_cache_alloc_page(nxt_mem_cache_pool_t *pool); +static nxt_mem_cache_block_t * + nxt_mem_cache_alloc_cluster(nxt_mem_cache_pool_t *pool); +static void *nxt_mem_cache_alloc_large(nxt_mem_cache_pool_t *pool, + size_t alignment, size_t size); +static nxt_int_t nxt_mem_cache_rbtree_compare(nxt_rbtree_node_t *node1, + nxt_rbtree_node_t *node2); +static nxt_mem_cache_block_t *nxt_mem_cache_find_block(nxt_rbtree_t *tree, + u_char *p); +static const char *nxt_mem_cache_chunk_free(nxt_mem_cache_pool_t *pool, + nxt_mem_cache_block_t *cluster, u_char *p); + + +nxt_mem_cache_pool_t * +nxt_mem_cache_pool_create(size_t cluster_size, size_t page_alignment, + size_t page_size, size_t min_chunk_size) +{ + /* Alignment and sizes must be a power of 2. */ + + if (nxt_slow_path((page_alignment & (page_alignment - 1)) != 0 + || (page_size & (page_size - 1)) != 0 + || (min_chunk_size & (min_chunk_size - 1)) != 0)) + { + return NULL; + } + + page_alignment = nxt_max(page_alignment, NXT_MAX_ALIGNMENT); + + if (nxt_slow_path(page_size < 64 + || page_size < page_alignment + || page_size < min_chunk_size + || min_chunk_size * 32 < page_size + || cluster_size < page_size + || cluster_size % page_size != 0)) + { + return NULL; + } + + return nxt_mem_cache_pool_fast_create(cluster_size, page_alignment, + page_size, min_chunk_size); +} + + +nxt_mem_cache_pool_t * +nxt_mem_cache_pool_fast_create(size_t cluster_size, size_t page_alignment, + size_t page_size, size_t min_chunk_size) +{ + nxt_uint_t slots, chunk_size; + nxt_mem_cache_slot_t *slot; + nxt_mem_cache_pool_t *pool; + + slots = 0; + chunk_size = page_size; + + do { + slots++; + chunk_size /= 2; + } while (chunk_size > min_chunk_size); + + pool = nxt_zalloc(sizeof(nxt_mem_cache_pool_t) + + slots * sizeof(nxt_mem_cache_slot_t)); + + if (nxt_fast_path(pool != NULL)) { + + pool->page_size = page_size; + pool->page_alignment = nxt_max(page_alignment, NXT_MAX_ALIGNMENT); + pool->cluster_size = cluster_size; + + slot = pool->slots; + + do { + nxt_queue_init(&slot->pages); + + slot->size = chunk_size; + /* slot->chunks should be one less than actual number of chunks. */ + slot->chunks = (page_size / chunk_size) - 1; + + slot++; + chunk_size *= 2; + } while (chunk_size < page_size); + + pool->chunk_size_shift = nxt_mem_cache_shift(min_chunk_size); + pool->page_size_shift = nxt_mem_cache_shift(page_size); + + nxt_rbtree_init(&pool->pages, nxt_mem_cache_rbtree_compare, NULL); + + nxt_queue_init(&pool->free_pages); + } + + return pool; +} + + +static nxt_uint_t +nxt_mem_cache_shift(nxt_uint_t n) +{ + nxt_uint_t shift; + + shift = 0; + n /= 2; + + do { + shift++; + n /= 2; + } while (n != 0); + + return shift; +} + + +nxt_bool_t +nxt_mem_cache_pool_is_empty(nxt_mem_cache_pool_t *pool) +{ + return (nxt_rbtree_is_empty(&pool->pages) + && nxt_queue_is_empty(&pool->free_pages)); +} + + +void +nxt_mem_cache_pool_destroy(nxt_mem_cache_pool_t *pool) +{ + void *p; + nxt_rbtree_node_t *node, *next; + nxt_mem_cache_block_t *block; + + for (node = nxt_rbtree_min(&pool->pages); + nxt_rbtree_is_there_successor(&pool->pages, node); + node = next) + { + next = nxt_rbtree_node_successor(&pool->pages, node); + + block = (nxt_mem_cache_block_t *) node; + + nxt_rbtree_delete(&pool->pages, &block->node); + + p = block->start; + + if (block->type != NXT_MEM_CACHE_EMBEDDED_BLOCK) { + nxt_free(block); + } + + nxt_free(p); + } + + nxt_free(pool); +} + + +nxt_inline u_char * +nxt_mem_cache_page_addr(nxt_mem_cache_pool_t *pool, nxt_mem_cache_page_t *page) +{ + nxt_mem_cache_block_t *block; + + block = (nxt_mem_cache_block_t *) + ((u_char *) page - page->number * sizeof(nxt_mem_cache_page_t) + - offsetof(nxt_mem_cache_block_t, pages)); + + return block->start + (page->number << pool->page_size_shift); +} + + +void * +nxt_mem_cache_alloc(nxt_mem_cache_pool_t *pool, size_t size) +{ + nxt_thread_log_debug("mem cache alloc: %uz", size); + + if (size <= pool->page_size) { + return nxt_mem_cache_alloc_small(pool, size); + } + + return nxt_mem_cache_alloc_large(pool, NXT_MAX_ALIGNMENT, size); +} + + +void * +nxt_mem_cache_zalloc(nxt_mem_cache_pool_t *pool, size_t size) +{ + void *p; + + p = nxt_mem_cache_alloc(pool, size); + + if (nxt_fast_path(p != NULL)) { + nxt_memzero(p, size); + } + + return p; +} + + +void * +nxt_mem_cache_align(nxt_mem_cache_pool_t *pool, size_t alignment, size_t size) +{ + nxt_thread_log_debug("mem cache align: @%uz:%uz", alignment, size); + + /* Alignment must be a power of 2. */ + + if (nxt_fast_path((alignment - 1) & alignment) == 0) { + + if (size <= pool->page_size && alignment <= pool->page_alignment) { + size = nxt_max(size, alignment); + + if (size <= pool->page_size) { + return nxt_mem_cache_alloc_small(pool, size); + } + } + + return nxt_mem_cache_alloc_large(pool, alignment, size); + } + + return NULL; +} + + +void * +nxt_mem_cache_zalign(nxt_mem_cache_pool_t *pool, size_t alignment, size_t size) +{ + void *p; + + p = nxt_mem_cache_align(pool, alignment, size); + + if (nxt_fast_path(p != NULL)) { + nxt_memzero(p, size); + } + + return p; +} + + +static void * +nxt_mem_cache_alloc_small(nxt_mem_cache_pool_t *pool, size_t size) +{ + u_char *p; + nxt_queue_link_t *link; + nxt_mem_cache_page_t *page; + nxt_mem_cache_slot_t *slot; + + p = NULL; + + if (size <= pool->page_size / 2) { + + /* Find a slot with appropriate chunk size. */ + for (slot = pool->slots; slot->size < size; slot++) { /* void */ } + + size = slot->size; + + if (nxt_fast_path(!nxt_queue_is_empty(&slot->pages))) { + + link = nxt_queue_first(&slot->pages); + page = nxt_queue_link_data(link, nxt_mem_cache_page_t, link); + + p = nxt_mem_cache_page_addr(pool, page); + p += nxt_mem_cache_alloc_chunk(page->map, size); + + page->chunks--; + + if (page->chunks == 0) { + /* + * Remove full page from the pool chunk slot list + * of pages with free chunks. + */ + nxt_queue_remove(&page->link); + } + + } else { + page = nxt_mem_cache_alloc_page(pool); + + if (nxt_fast_path(page != NULL)) { + + nxt_queue_insert_head(&slot->pages, &page->link); + + /* Mark the first chunk as busy. */ + page->map[0] = 0x80; + page->map[1] = 0; + page->map[2] = 0; + page->map[3] = 0; + + /* slot->chunks are already one less. */ + page->chunks = slot->chunks; + page->size = size >> pool->chunk_size_shift; + + p = nxt_mem_cache_page_addr(pool, page); + } + } + + } else { + page = nxt_mem_cache_alloc_page(pool); + + if (nxt_fast_path(page != NULL)) { + page->size = pool->page_size >> pool->chunk_size_shift; + + p = nxt_mem_cache_page_addr(pool, page); + } + +#if (NXT_DEBUG) + size = pool->page_size; +#endif + } + + nxt_thread_log_debug("mem cache chunk:%uz alloc: %p", size, p); + + return p; +} + + +static nxt_uint_t +nxt_mem_cache_alloc_chunk(uint8_t *map, nxt_uint_t size) +{ + uint8_t mask; + nxt_uint_t n, offset; + + offset = 0; + n = 0; + + /* The page must have at least one free chunk. */ + + for ( ;; ) { + if (map[n] != 0xff) { + + mask = 0x80; + + do { + if ((map[n] & mask) == 0) { + /* A free chunk is found. */ + map[n] |= mask; + return offset; + } + + offset += size; + mask >>= 1; + + } while (mask != 0); + + } else { + /* Fast-forward: all 8 chunks are occupied. */ + offset += size * 8; + } + + n++; + } +} + + +static nxt_mem_cache_page_t * +nxt_mem_cache_alloc_page(nxt_mem_cache_pool_t *pool) +{ + nxt_queue_link_t *link; + nxt_mem_cache_page_t *page; + nxt_mem_cache_block_t *cluster; + + if (nxt_queue_is_empty(&pool->free_pages)) { + cluster = nxt_mem_cache_alloc_cluster(pool); + if (nxt_slow_path(cluster == NULL)) { + return NULL; + } + } + + link = nxt_queue_first(&pool->free_pages); + nxt_queue_remove(link); + + page = nxt_queue_link_data(link, nxt_mem_cache_page_t, link); + + return page; +} + + +static nxt_mem_cache_block_t * +nxt_mem_cache_alloc_cluster(nxt_mem_cache_pool_t *pool) +{ + nxt_uint_t n; + nxt_mem_cache_block_t *cluster; + + n = pool->cluster_size >> pool->page_size_shift; + + cluster = nxt_zalloc(sizeof(nxt_mem_cache_block_t) + + n * sizeof(nxt_mem_cache_page_t)); + + if (nxt_slow_path(cluster == NULL)) { + return NULL; + } + + /* NXT_MEM_CACHE_CLUSTER_BLOCK type is zero. */ + + cluster->size = pool->cluster_size; + + cluster->start = nxt_memalign(pool->page_alignment, pool->cluster_size); + if (nxt_slow_path(cluster->start == NULL)) { + nxt_free(cluster); + return NULL; + } + + n--; + cluster->pages[n].number = n; + nxt_queue_insert_head(&pool->free_pages, &cluster->pages[n].link); + + while (n != 0) { + n--; + cluster->pages[n].number = n; + nxt_queue_insert_before(&cluster->pages[n + 1].link, + &cluster->pages[n].link); + } + + nxt_rbtree_insert(&pool->pages, &cluster->node); + + return cluster; +} + + +static void * +nxt_mem_cache_alloc_large(nxt_mem_cache_pool_t *pool, size_t alignment, + size_t size) +{ + u_char *p; + size_t aligned_size; + uint8_t type; + nxt_mem_cache_block_t *block; + + if (nxt_slow_path((size - 1) & size) != 0) { + aligned_size = nxt_align_size(size, sizeof(uintptr_t)); + + p = nxt_memalign(alignment, + aligned_size + sizeof(nxt_mem_cache_block_t)); + + if (nxt_slow_path(p == NULL)) { + return NULL; + } + + block = (nxt_mem_cache_block_t *) (p + aligned_size); + type = NXT_MEM_CACHE_EMBEDDED_BLOCK; + + } else { + block = nxt_malloc(sizeof(nxt_mem_cache_block_t)); + if (nxt_slow_path(block == NULL)) { + nxt_free(block); + return NULL; + } + + p = nxt_memalign(alignment, size); + if (nxt_slow_path(p == NULL)) { + return NULL; + } + + type = NXT_MEM_CACHE_DISCRETE_BLOCK; + } + + block->type = type; + block->size = size; + block->start = p; + + nxt_rbtree_insert(&pool->pages, &block->node); + + return p; +} + + +static nxt_int_t +nxt_mem_cache_rbtree_compare(nxt_rbtree_node_t *node1, nxt_rbtree_node_t *node2) +{ + nxt_mem_cache_block_t *block1, *block2; + + block1 = (nxt_mem_cache_block_t *) node1; + block2 = (nxt_mem_cache_block_t *) node2; + + return (uintptr_t) block1->start - (uintptr_t) block2->start; +} + + +void +nxt_mem_cache_free(nxt_mem_cache_pool_t *pool, void *p) +{ + const char *err; + nxt_mem_cache_block_t *block; + + nxt_thread_log_debug("mem cache free %p", p); + + block = nxt_mem_cache_find_block(&pool->pages, p); + + if (nxt_fast_path(block != NULL)) { + + if (block->type == NXT_MEM_CACHE_CLUSTER_BLOCK) { + err = nxt_mem_cache_chunk_free(pool, block, p); + + } else if (nxt_fast_path(p == block->start)) { + nxt_rbtree_delete(&pool->pages, &block->node); + + if (block->type == NXT_MEM_CACHE_DISCRETE_BLOCK) { + nxt_free(block); + } + + nxt_free(p); + + err = NULL; + + } else { + err = "pointer to wrong page"; + } + + } else { + err = "pointer is out of pool"; + } + + if (nxt_slow_path(err != NULL)) { + nxt_thread_log_alert("nxt_mem_cache_pool_free(%p): %s", p, err); + } +} + + +static nxt_mem_cache_block_t * +nxt_mem_cache_find_block(nxt_rbtree_t *tree, u_char *p) +{ + nxt_rbtree_node_t *node, *sentinel; + nxt_mem_cache_block_t *block; + + node = nxt_rbtree_root(tree); + sentinel = nxt_rbtree_sentinel(tree); + + while (node != sentinel) { + + block = (nxt_mem_cache_block_t *) node; + + if (p < block->start) { + node = node->left; + + } else if (p >= block->start + block->size) { + node = node->right; + + } else { + return block; + } + } + + return NULL; +} + + +static const char * +nxt_mem_cache_chunk_free(nxt_mem_cache_pool_t *pool, + nxt_mem_cache_block_t *cluster, u_char *p) +{ + u_char *start; + uintptr_t offset; + nxt_uint_t n, size, chunk; + nxt_mem_cache_page_t *page; + nxt_mem_cache_slot_t *slot; + + n = (p - cluster->start) >> pool->page_size_shift; + start = cluster->start + (n << pool->page_size_shift); + + page = &cluster->pages[n]; + + if (page->size == 0) { + return "page is already free"; + } + + size = page->size << pool->chunk_size_shift; + + if (size != pool->page_size) { + + offset = (uintptr_t) (p - start) & (pool->page_size - 1); + chunk = offset / size; + + if (nxt_slow_path(offset != chunk * size)) { + return "pointer to wrong chunk"; + } + + if (nxt_slow_path(nxt_mem_cache_chunk_is_free(page->map, chunk))) { + return "chunk is already free"; + } + + nxt_mem_cache_chunk_set_free(page->map, chunk); + + /* Find a slot with appropriate chunk size. */ + for (slot = pool->slots; slot->size < size; slot++) { /* void */ } + + if (page->chunks != slot->chunks) { + page->chunks++; + + if (page->chunks == 1) { + /* + * Add the page to the head of pool chunk slot list + * of pages with free chunks. + */ + nxt_queue_insert_head(&slot->pages, &page->link); + } + + nxt_mem_cache_free_junk(p, size); + + return NULL; + + } else { + /* + * All chunks are free, remove the page from pool chunk slot + * list of pages with free chunks. + */ + nxt_queue_remove(&page->link); + } + + } else if (nxt_slow_path(p != start)) { + return "invalid pointer to chunk"; + } + + /* Add the free page to the pool's free pages tree. */ + + page->size = 0; + nxt_queue_insert_head(&pool->free_pages, &page->link); + + nxt_mem_cache_free_junk(p, size); + + /* Test if all pages in the cluster are free. */ + + page = cluster->pages; + n = pool->cluster_size >> pool->page_size_shift; + + do { + if (page->size != 0) { + return NULL; + } + + page++; + n--; + } while (n != 0); + + /* Free cluster. */ + + page = cluster->pages; + n = pool->cluster_size >> pool->page_size_shift; + + do { + nxt_queue_remove(&page->link); + page++; + n--; + } while (n != 0); + + nxt_rbtree_delete(&pool->pages, &cluster->node); + + p = cluster->start; + + nxt_free(cluster); + nxt_free(p); + + return NULL; +} + + +const nxt_mem_proto_t nxt_mem_cache_proto = { + (nxt_mem_proto_alloc_t) nxt_mem_cache_alloc, + (nxt_mem_proto_free_t) nxt_mem_cache_free, +}; |