├── .gitignore ├── Makefile ├── README ├── mpool.c ├── mpool.h └── test.c /.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | *.o 3 | *.a 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | COPTS= -O2 -Wall 2 | # COPTS+= -g 3 | # COPTS+= -DNDEBUG 4 | # COPTS+= -p 5 | 6 | all: libmpool.a 7 | 8 | mpool.o: mpool.c Makefile 9 | ${CC} -c mpool.c ${COPTS} 10 | 11 | libmpool.a: mpool.o 12 | ${AR} rl $@ mpool.o 13 | 14 | mpool.c: mpool.h 15 | 16 | test: test.c libmpool.a 17 | ${CC} -o $@ test.c -L. -lmpool ${COPTS} 18 | ./test 19 | 20 | clean: 21 | rm -f *.o *.a test *.core gmon.out 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | A memory pool allocator, designed for systems that need to allocate/free 2 | pointers in amortized O(1) time. Memory is allocated a page at a time, 3 | then added to a set of pools of equally sized regions. A free list for 4 | each size is maintained in the unused regions. When a pointer is 5 | repooled, it is put at the head of the appropriate free list. 6 | 7 | Allocations larger than mp->max_pool (configurable, usually over 2048 8 | bytes) are allocated whole via mmap and freed immediately via munmap; no 9 | free list is used. 10 | 11 | For example usage, see test.c. 12 | -------------------------------------------------------------------------------- /mpool.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Scott Vokes 3 | * 4 | * Permission to use, copy, modify, and/or distribute this software for any 5 | * purpose with or without fee is hereby granted, provided that the above 6 | * copyright notice and this permission notice appear in all copies. 7 | * 8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 | */ 16 | 17 | /* 18 | * A memory pool allocator, designed for systems that need to 19 | * allocate/free pointers in amortized O(1) time. Memory is allocated a 20 | * page at a time, then added to a set of pools of equally sized 21 | * regions. A free list for each size is maintained in the unused 22 | * regions. When a pointer is repooled, it is linked back into the 23 | * pool with the given size's free list. 24 | * 25 | * Note that repooling with the wrong size leads to subtle/ugly memory 26 | * clobbering bugs. Turning on memory use logging via MPOOL_DEBUG 27 | * can help pin down the location of most such errors. 28 | * 29 | * Allocations larger than the page size are allocated whole via 30 | * mmap, and those larger than mp->max_pool (configurable) are 31 | * freed immediately via munmap; no free list is used. 32 | */ 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include "mpool.h" 43 | 44 | #define DBG MPOOL_DEBUG 45 | 46 | static void *get_mmap(long sz) { 47 | void *p = mmap(0, sz, PROT_READ|PROT_WRITE, 48 | MAP_PRIVATE|MAP_ANON, -1, 0); 49 | if (p == MAP_FAILED) return NULL; 50 | return p; 51 | } 52 | 53 | /* Optimized base-2 integer ceiling, from _Hacker's Delight_ 54 | * by Henry S. Warren, pg. 48. Called 'clp2' there. */ 55 | static unsigned int iceil2(unsigned int x) { 56 | x = x - 1; 57 | x = x | (x >> 1); 58 | x = x | (x >> 2); 59 | x = x | (x >> 4); 60 | x = x | (x >> 8); 61 | x = x | (x >> 16); 62 | return x + 1; 63 | } 64 | 65 | /* mmap a new memory pool of TOTAL_SZ bytes, then build an internal 66 | * freelist of SZ-byte cells, with the head at (result)[0]. 67 | * Returns NULL on error. */ 68 | void **mpool_new_pool(unsigned int sz, unsigned int total_sz) { 69 | void *p = get_mmap(sz > total_sz ? sz : total_sz); 70 | int i, o=0, lim; /* o=offset */ 71 | int **pool; 72 | void *last = NULL; 73 | if (p == NULL) return NULL; 74 | pool = (int **)p;; 75 | assert(pool); 76 | assert(sz > sizeof(void *)); 77 | 78 | lim = (total_sz/sz); 79 | if (DBG) fprintf(stderr, 80 | "mpool_new_pool sz: %d lim: %d => %d %p\n", 81 | sz, lim, lim * sz, p); 82 | for (i=0; i 1) fprintf(stderr, "%d (%d / 0x%04x) -> %p = %p\n", 88 | i, o, o, &pool[o], pool[o]); 89 | } 90 | pool[o] = NULL; 91 | return p; 92 | } 93 | 94 | /* Add a new pool, resizing the pool array if necessary. */ 95 | static int add_pool(mpool *mp, void *p, int sz) { 96 | void **nps, *nsizes; /* new pools, new sizes */ 97 | assert(p); 98 | assert(sz > 0); 99 | if (DBG) fprintf(stderr, "mpool_add_pool (%d / %d) @ %p, sz %d\n", 100 | mp->ct, mp->pal, p, sz); 101 | if (mp->ct == mp->pal) { 102 | mp->pal *= 2; /* ram will exhaust before overflow... */ 103 | nps = MPOOL_REALLOC(mp->ps, mp->pal * sizeof(void **)); 104 | nsizes = MPOOL_REALLOC(mp->sizes, mp->pal * sizeof(int *)); 105 | if (nps == NULL || nsizes == NULL) return -1; 106 | mp->sizes = nsizes; 107 | mp->ps = nps; 108 | } 109 | 110 | mp->ps[mp->ct] = p; 111 | mp->sizes[mp->ct] = sz; 112 | mp->ct++; 113 | return 0; 114 | } 115 | 116 | /* Initialize a memory pool set, with pools in sizes 117 | * 2^min2 to 2^max2. Returns NULL on error. */ 118 | mpool *mpool_init(int min2, int max2) { 119 | int palen; /* length of pool array */ 120 | int ct = ct = max2 - min2 + 1; /* pool array count */ 121 | long pgsz = sysconf(_SC_PAGESIZE); 122 | mpool *mp; 123 | void *pools; 124 | int *sizes; 125 | 126 | palen = iceil2(ct); 127 | if (DBG) fprintf(stderr, "mpool_init for cells %d - %d bytes\n", 128 | 1 << min2, 1 << max2); 129 | 130 | assert(ct > 0); 131 | mp = MPOOL_MALLOC(sizeof(mpool) + (ct-1)*sizeof(void *)); 132 | pools = MPOOL_MALLOC(palen*sizeof(void **)); 133 | sizes = MPOOL_MALLOC(palen*sizeof(int)); 134 | if (mp == NULL || pools == NULL || sizes == NULL) return NULL; 135 | mp->ct = ct; 136 | mp->ps = pools; 137 | mp->pal = palen; 138 | mp->pg_sz = pgsz; 139 | mp->sizes = sizes; 140 | mp->min_pool = 1 << min2; 141 | mp->max_pool = 1 << max2; 142 | bzero(sizes, palen * sizeof(int)); 143 | bzero(pools, palen * sizeof(void *)); 144 | bzero(mp->hs, ct * sizeof(void *)); 145 | 146 | return mp; 147 | } 148 | 149 | /* Free a memory pool set. */ 150 | void mpool_free(mpool *mp) { 151 | long i, sz, pgsz = mp->pg_sz; 152 | assert(mp); 153 | if (DBG) fprintf(stderr, "%d/%d pools, freeing...\n", mp->ct, mp->pal); 154 | for (i=0; ict; i++) { 155 | void *p = mp->ps[i]; 156 | if (p) { 157 | sz = mp->sizes[i]; 158 | assert(sz > 0); 159 | sz = sz >= pgsz ? sz : pgsz; 160 | if (DBG) fprintf(stderr, "mpool_free %ld, sz %ld (%p)\n", i, sz, mp->ps[i]); 161 | if (munmap(mp->ps[i], sz) == -1) { 162 | fprintf(stderr, "munmap error while unmapping %lu bytes at %p\n", 163 | sz, mp->ps[i]); 164 | } 165 | } 166 | } 167 | MPOOL_FREE(mp->ps, mp->ct * sizeof(*ps)); 168 | MPOOL_FREE(mp, sizeof(*mp)); 169 | } 170 | 171 | /* Allocate memory out of the relevant memory pool. 172 | * If larger than max_pool, just mmap it. If pool is full, mmap a new one and 173 | * link it to the end of the current one. Returns NULL on error. */ 174 | void *mpool_alloc(mpool *mp, int sz) { 175 | void **cur, **np; /* new pool */ 176 | int i, p, szceil = 0; 177 | assert(mp); 178 | if (sz >= mp->max_pool) { 179 | cur = get_mmap(sz); /* just mmap it */ 180 | if (cur == NULL) return NULL; 181 | if (DBG) fprintf(stderr, 182 | "mpool_alloc mmap %d bytes @ %p\n", sz, cur); 183 | return cur; 184 | } 185 | 186 | for (i=0, p=mp->min_pool; ; i++, p*=2) { 187 | if (p > sz) { szceil = p; break; } 188 | } 189 | assert(szceil > 0); 190 | cur = mp->hs[i]; /* get current head */ 191 | if (cur == NULL) { /* lazily allocate & init pool */ 192 | void **pool = mpool_new_pool(szceil, mp->pg_sz); 193 | if (pool == NULL) return NULL; 194 | mp->ps[i] = pool; 195 | mp->hs[i] = &pool[0]; 196 | mp->sizes[i] = szceil; 197 | cur = mp->hs[i]; 198 | } 199 | assert(cur); 200 | 201 | if (*cur == NULL) { /* if at end, attach to a new page */ 202 | if (DBG) fprintf(stderr, 203 | "mpool_alloc adding pool w/ cell size %d\n", szceil); 204 | np = mpool_new_pool(szceil, mp->pg_sz); 205 | if (np == NULL) return NULL; 206 | *cur = &np[0]; 207 | assert(*cur); 208 | if (add_pool(mp, np, szceil) < 0) return NULL; 209 | } 210 | 211 | assert(*cur > (void *)4096); 212 | if (DBG) fprintf(stderr, 213 | "mpool_alloc pool %d bytes @ %p (list %d, szceil %d )\n", 214 | sz, (void*) cur, i, szceil); 215 | 216 | mp->hs[i] = *cur; /* set head to next head */ 217 | return cur; 218 | } 219 | 220 | /* Push an individual pointer P back on the freelist for 221 | * the pool with size SZ cells. 222 | * if SZ is > the max pool size, just munmap it. */ 223 | void mpool_repool(mpool *mp, void *p, int sz) { 224 | int i=0, szceil, max_pool = mp->max_pool; 225 | void **ip; 226 | 227 | if (sz > max_pool) { 228 | if (DBG) fprintf(stderr, "mpool_repool munmap sz %d @ %p\n", sz, p); 229 | if (munmap(p, sz) == -1) { 230 | fprintf(stderr, "munmap error while unmapping %d bytes at %p\n", 231 | sz, p); 232 | } 233 | return; 234 | } 235 | 236 | szceil = iceil2(sz); 237 | szceil = szceil > mp->min_pool ? szceil : mp->min_pool; 238 | 239 | ip = (void **)p; 240 | *ip = mp->hs[i]; 241 | assert(ip); 242 | mp->hs[i] = ip; 243 | if (DBG) fprintf(stderr, 244 | "mpool_repool list %d, %d bytes (ceil %d): %p\n", 245 | i, sz, szceil, ip); 246 | } 247 | 248 | /* Reallocate data, growing or shrinking and copying the contents. 249 | * Returns NULL on reallocation error. */ 250 | void *mpool_realloc(mpool *mp, void *p, int old_sz, int new_sz) { 251 | void *r = mpool_alloc(mp, new_sz); 252 | if (r == NULL) return NULL; 253 | memcpy(r, p, old_sz); 254 | mpool_repool(mp, p, old_sz); 255 | return r; 256 | } 257 | -------------------------------------------------------------------------------- /mpool.h: -------------------------------------------------------------------------------- 1 | #ifndef MPOOL_H 2 | #define MPOOL_H 3 | 4 | /* Turn on debugging traces */ 5 | #ifndef MPOOL_DEBUG 6 | #define MPOOL_DEBUG 0 7 | #endif 8 | 9 | /* Allow overriding malloc functions. */ 10 | #ifndef MPOOL_MALLOC 11 | #define MPOOL_MALLOC(sz) malloc(sz) 12 | #define MPOOL_REALLOC(p, sz) realloc(p, sz) 13 | #define MPOOL_FREE(p, sz) free(p) 14 | #endif 15 | 16 | typedef struct { 17 | int ct; /* actual pool count */ 18 | int pal; /* pool array length (2^x ceil of ct) */ 19 | int min_pool; /* minimum pool size */ 20 | int max_pool; /* maximum pool size */ 21 | int pg_sz; /* page size, typically 4096 */ 22 | void **ps; /* pools */ 23 | int *sizes; /* chunk size for each pool */ 24 | void *hs[1]; /* heads for pools' free lists */ 25 | } mpool; 26 | 27 | /* Initialize a memory pool for allocations between 2^min2 and 2^max2, 28 | * inclusive. (Larger allocations will be directly allocated and freed 29 | * via mmap / munmap.) */ 30 | mpool *mpool_init(int min2, int max2); 31 | 32 | /* Allocate SZ bytes. */ 33 | void *mpool_alloc(mpool *mp, int sz); 34 | 35 | /* mmap a new memory pool of TOTAL_SZ bytes, then build an internal 36 | * freelist of SZ-byte cells, with the head at (result)[0]. */ 37 | void **mpool_new_pool(unsigned int sz, unsigned int total_sz); 38 | 39 | /* Return pointer P (SZ bytes in size) to the appropriate pool. */ 40 | void mpool_repool(mpool *mp, void *p, int sz); 41 | 42 | /* Resize P from OLD_SZ to NEW_SZ, copying content. */ 43 | void *mpool_realloc(mpool *mp, void *p, int old_sz, int new_sz); 44 | 45 | /* Free the pool. */ 46 | void mpool_free(mpool *mp); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | /* Randomly test / stress the allocator */ 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include "mpool.h" 8 | 9 | #define PMAX 11 10 | 11 | int main(int argc, char **argv) { 12 | int i, sz, max_pool_sz; 13 | long seed; 14 | int *ip; 15 | 16 | /* Init a new mpool for values 2^4 to 2^PMAX */ 17 | mpool *mp = mpool_init(4, PMAX); 18 | max_pool_sz = mp->max_pool; 19 | if (argc > 1) { 20 | seed = atol(argv[1]); 21 | } else { 22 | struct timeval tv; 23 | if (gettimeofday(&tv, NULL) < 0) { 24 | fprintf(stderr, "gettimeofday fail\n"); 25 | return 1; 26 | } 27 | seed = tv.tv_usec; 28 | } 29 | srandom(seed); 30 | printf("seed is %ld\n", seed); 31 | 32 | for (i=0; i<5000000; i++) { 33 | sz = random() % 64; 34 | /* also alloc some larger chunks */ 35 | if (random() % 100 == 0) sz = random() % 10000; 36 | sz = sz ? sz : 1; /* no 0 allocs */ 37 | ip = (int *)mpool_alloc(mp, sz); 38 | *ip = 7; 39 | 40 | /* randomly repool some of them */ 41 | if (random() % 10 == 0) { /* repool, known size */ 42 | mpool_repool(mp, ip, sz); 43 | } 44 | if (i % 10000 == 0 && i > 0) { 45 | putchar('.'); 46 | if (i % 700000 == 0) putchar('\n'); 47 | } 48 | } 49 | 50 | mpool_free(mp); 51 | putchar('\n'); 52 | return 0; 53 | } 54 | --------------------------------------------------------------------------------