├── LICENSE ├── Makefile ├── README.md ├── bin └── .gitignore ├── fmpool.h └── test ├── example ├── Makefile └── example.c ├── functest ├── Makefile └── functest.c └── perftest ├── Makefile └── perftest.c /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015-2017 David Newman 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all functest perftest example 2 | 3 | all: functest perftest example 4 | 5 | functest: 6 | cd test/functest; make 7 | 8 | perftest: 9 | cd test/perftest; make 10 | 11 | example: 12 | cd test/example; make 13 | 14 | clean: 15 | cd test/functest; make clean 16 | cd test/perftest; make clean 17 | cd test/example; make clean 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Free-List Memory Pool 2 | ------- 3 | This is a C99 header library that provides efficient type-generic code to allocate heap memory from the OS for user-defined number of objects. Achieved through the magic of C macros and [free lists](http://en.wikipedia.org/wiki/Free_list). 4 | 5 | This library is built for situations where programs repeatedly call malloc/calloc/new/etc. for small data structures, followed closely by free/delete. Once a pool is created, getting a pointer from the pool to an unused object costs a couple of machine instructions. Boost memory pools and others are overkill sometimes. 6 | 7 | Limitations: 8 | * Memory pools are statically-sized. 9 | 10 | Inspired by [computer game particles](http://gameprogrammingpatterns.com/object-pool.html). 11 | 12 | ## Try it now 13 | ```sh 14 | make; ./bin/perftest 15 | ``` 16 | 17 | ## Example 18 | ```c 19 | #include "fmpool.h" 20 | 21 | /* define a "point" struct with x and y coordinates as doubles */ 22 | typedef struct Point_s 23 | { 24 | double x; 25 | double y; 26 | } Point_t; 27 | 28 | /* macro that initializes fmpool functions that take Point_t structs as 29 | arguments - compile-time only */ 30 | FMPOOL_INIT(Point_t) 31 | 32 | #define NUM_POINTS 16 33 | 34 | int main() 35 | { 36 | /* create pool - requests memory from OS */ 37 | fmpool_t(Point_t)* Pool; 38 | Pool = fmpool_create(Point_t, NUM_POINTS); 39 | 40 | /* grab some pointers to allocated memory from the pool */ 41 | Point_t* PointArray[NUM_POINTS] = {NULL}; 42 | for(size_t i = 0; i < NUM_POINTS; i++) 43 | { 44 | Point_t* Point = fmpool_get(Point_t, Pool); 45 | if(Point) 46 | PointArray[i] = Point; 47 | } 48 | /* release some points back to the pool */ 49 | for(size_t i = 0; i < NUM_POINTS - 5; i++) 50 | fmpool_free(Point_t, PointArray[i], Pool); 51 | 52 | /* destroy pool when done - releases memory to OS */ 53 | fmpool_destroy(Point_t, Pool); 54 | return 0; 55 | } 56 | ``` 57 | 58 | ## Details 59 | FMPool requests a contiguous block of memory once during creation with `fmpool_create`. Afterwards, pointers to unused objects are provided with `fmpool_get`. Objects can be released back to the pool with `fmpool_free` and subsequently re-requested. When finished, the memory is released back to the OS with `fmpool_destroy`. 60 | 61 | ## Functions 62 | #### fmpool_create(TYPE, NUM) 63 | Creates a new memory pool. The new pool has `NUM` objects of type `TYPE`. 64 | >**Return Values** 65 | >On success: returns a pointer to the new fmpool. 66 | >On failure: returns a `NULL` pointer. 67 | 68 | #### fmpool_destroy(TYPE, POOL) 69 | Frees all memory associated with `POOL`. 70 | >**Return Values** 71 | >Returns no value. (mimics C99 standard). 72 | 73 | #### fmpool_get(TYPE, POOL) 74 | Grabs an unused object from the pool. 75 | >**Return Values** 76 | >On success: returns a `TYPE` pointer. 77 | >On failure: returns a `NULL` pointer. 78 | 79 | #### fmpool_free(TYPE, OBJ, POOL) 80 | If `OBJ` is controlled by `POOL`, sets it as unused. Does not zero memory. 81 | >**Return Values** 82 | >On success: returns `true`. 83 | >On failure: returns `false`. 84 | 85 | ## License 86 | [MIT License](http://dn.mit-license.org) 87 | 88 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore everything in this directory 2 | * 3 | # Except this file 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /fmpool.h: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | Copyright © 2015-2023 David Newman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the “Software”), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | #ifndef _FMPOOL_H_INCLUDED_ 25 | #define _FMPOOL_H_INCLUDED_ 26 | 27 | #include /* size_t */ 28 | #include /* calloc, free */ 29 | #include /* bool */ 30 | 31 | #define FMPOOL_INLINE __attribute__((always_inline)) /* TODO: ? */ 32 | 33 | #define FMPOOL_INIT(TYPE) \ 34 | typedef union fmpool_##TYPE##_item_s fmpool_##TYPE##_item_t; \ 35 | union fmpool_##TYPE##_item_s \ 36 | { \ 37 | fmpool_##TYPE##_item_t* next; \ 38 | TYPE data; \ 39 | }; \ 40 | typedef struct fmpool_##TYPE##_s \ 41 | { \ 42 | fmpool_##TYPE##_item_t* items; \ 43 | fmpool_##TYPE##_item_t* head; \ 44 | size_t num; \ 45 | } fmpool_##TYPE##_t; \ 46 | static FMPOOL_INLINE fmpool_##TYPE##_t* \ 47 | fmpool_##TYPE##_create(const size_t num) \ 48 | { \ 49 | if(num == 0) \ 50 | { \ 51 | return NULL; /* creating pool with zero items */ \ 52 | } \ 53 | fmpool_##TYPE##_t* P; \ 54 | P = calloc(1, sizeof(fmpool_##TYPE##_t)); \ 55 | if(P == NULL) \ 56 | { \ 57 | return NULL; /* calloc failed */ \ 58 | } \ 59 | P->items = calloc(num, sizeof(fmpool_##TYPE##_item_t)); \ 60 | if(P->items == NULL) \ 61 | { \ 62 | free(P); \ 63 | return NULL; /* calloc failed */ \ 64 | } \ 65 | P->head = &P->items[0]; \ 66 | P->num = num; \ 67 | for(size_t i = 0; i < num - 1; i++) \ 68 | { \ 69 | P->items[i].next = &P->items[i + 1]; \ 70 | } \ 71 | P->items[num - 1].next = NULL; \ 72 | return P; \ 73 | } \ 74 | static FMPOOL_INLINE void fmpool_##TYPE##_destroy(fmpool_##TYPE##_t* P) \ 75 | { \ 76 | free(P->items); \ 77 | free(P); \ 78 | } \ 79 | static FMPOOL_INLINE TYPE* fmpool_##TYPE##_get(fmpool_##TYPE##_t* P) \ 80 | { \ 81 | fmpool_##TYPE##_item_t* item = P->head; \ 82 | if(item == NULL) \ 83 | { \ 84 | return NULL; \ 85 | } \ 86 | P->head = item->next; \ 87 | return &item->data; \ 88 | } \ 89 | static FMPOOL_INLINE bool fmpool_##TYPE##_free(TYPE* OBJ, \ 90 | fmpool_##TYPE##_t* P) \ 91 | { \ 92 | fmpool_##TYPE##_item_t* I = (fmpool_##TYPE##_item_t*)OBJ;\ 93 | if((I < P->items) || (I >= (P->items + P->num))) \ 94 | { \ 95 | return false; \ 96 | } \ 97 | I->next = P->head; \ 98 | P->head = I; \ 99 | return true; \ 100 | } 101 | 102 | #define fmpool_t(TYPE) fmpool_##TYPE##_t 103 | #define fmpool_create(TYPE, NUM) fmpool_##TYPE##_create(NUM) 104 | #define fmpool_destroy(TYPE, POOL) fmpool_##TYPE##_destroy(POOL) 105 | #define fmpool_get(TYPE, POOL) fmpool_##TYPE##_get(POOL) 106 | #define fmpool_free(TYPE, OBJ, POOL) fmpool_##TYPE##_free(OBJ, POOL) 107 | 108 | #endif // _FMPOOL_H_INCLUDED_ 109 | 110 | -------------------------------------------------------------------------------- /test/example/Makefile: -------------------------------------------------------------------------------- 1 | NAME = example 2 | 3 | OBJDIR = ../../bin 4 | 5 | CFLAGS = 6 | CFLAGS += -g 7 | CFLAGS += -Wall 8 | CFLAGS += --std=c99 9 | CFLAGS += -Wno-unused-function 10 | CFLAGS += -fno-inline 11 | CFLAGS += -I../.. 12 | 13 | all: 14 | $(CC) $(CFLAGS) $(NAME).c -o $(OBJDIR)/$(NAME) 15 | 16 | clean: 17 | rm -fr $(OBJDIR)/$(NAME).o $(OBJDIR)/$(NAME) $(OBJDIR)/$(NAME).dSYM 18 | -------------------------------------------------------------------------------- /test/example/example.c: -------------------------------------------------------------------------------- 1 | #include "fmpool.h" 2 | 3 | /* define a "point" struct with x and y coordinates as doubles */ 4 | typedef struct Point_s 5 | { 6 | double x; 7 | double y; 8 | } Point_t; 9 | 10 | /* macro that initializes fmpool functions that take Point_t structs as 11 | arguments - compile-time only */ 12 | FMPOOL_INIT(Point_t) 13 | 14 | #define NUM_POINTS 16 15 | 16 | int main() 17 | { 18 | /* create pool - requests memory from OS */ 19 | fmpool_t(Point_t)* Pool; 20 | Pool = fmpool_create(Point_t, NUM_POINTS); 21 | 22 | /* grab some pointers to allocated memory from the pool */ 23 | Point_t* PointArray[NUM_POINTS] = {NULL}; 24 | for(size_t i = 0; i < NUM_POINTS; i++) 25 | { 26 | Point_t* Point = fmpool_get(Point_t, Pool); 27 | if(Point) 28 | PointArray[i] = Point; 29 | } 30 | /* release some points back to the pool */ 31 | for(size_t i = 0; i < NUM_POINTS - 5; i++) 32 | fmpool_free(Point_t, PointArray[i], Pool); 33 | 34 | /* destroy pool when done - releases memory to OS */ 35 | fmpool_destroy(Point_t, Pool); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /test/functest/Makefile: -------------------------------------------------------------------------------- 1 | NAME = functest 2 | 3 | OBJDIR = ../../bin 4 | 5 | CFLAGS = 6 | CFLAGS += -g 7 | CFLAGS += -Wall 8 | CFLAGS += --std=c99 9 | CFLAGS += -Wno-unused-function 10 | CFLAGS += -fno-inline 11 | CFLAGS += -I../.. 12 | 13 | all: 14 | $(CC) $(CFLAGS) $(NAME).c -o $(OBJDIR)/$(NAME) 15 | 16 | clean: 17 | rm -fr $(OBJDIR)/$(NAME).o $(OBJDIR)/$(NAME) $(OBJDIR)/$(NAME).dSYM 18 | -------------------------------------------------------------------------------- /test/functest/functest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fmpool.h" 5 | 6 | typedef struct Point_s 7 | { 8 | double x; 9 | double y; 10 | } Point_t; 11 | 12 | FMPOOL_INIT(Point_t) 13 | 14 | #define FAILTEST(testcond,failure_msg) \ 15 | if((testcond)) { \ 16 | fputs(failure_msg,stderr); fputc('\n',stderr); \ 17 | exit(EXIT_FAILURE); \ 18 | } 19 | 20 | #define NUMITEMS 10 21 | static void free_under_and_overflow_test(const char* failure_msg) { 22 | fmpool_t(Point_t)* pool = fmpool_create(Point_t,NUMITEMS); 23 | Point_t* p = fmpool_get(Point_t,pool); 24 | /* test the underflow case */ 25 | FAILTEST(fmpool_free(Point_t,p-1,pool) != false,failure_msg); 26 | /* test the overflow case */ 27 | FAILTEST(fmpool_free(Point_t,p+NUMITEMS,pool) != false,failure_msg); 28 | FAILTEST(fmpool_free(Point_t,p,pool) != true,failure_msg); 29 | if(pool) { 30 | fmpool_destroy(Point_t, pool); 31 | } 32 | } 33 | static void zero_allocation_test(const char* failure_msg) { 34 | fmpool_t(Point_t)* p; 35 | FAILTEST(((p = fmpool_create(Point_t,0)) != NULL),failure_msg); 36 | if(p) { 37 | fmpool_destroy(Point_t, p); 38 | } 39 | } 40 | 41 | static void* allocate_test(const size_t num, 42 | bool (*testfn)(fmpool_t(Point_t)*), 43 | const char* failure_msg) 44 | { 45 | fmpool_t(Point_t)* p; 46 | FAILTEST(((p = fmpool_create(Point_t,num)) == NULL),failure_msg); 47 | FAILTEST((testfn(p) == false),failure_msg); 48 | fmpool_destroy(Point_t, p); 49 | return NULL; 50 | } 51 | 52 | static bool one_allocation_test(fmpool_t(Point_t)* pool) { 53 | return(fmpool_get(Point_t,pool) != NULL && 54 | fmpool_get(Point_t,pool) == NULL); 55 | } 56 | 57 | static bool freelist_simple_test(fmpool_t(Point_t)* pool) { 58 | Point_t* p = fmpool_get(Point_t,pool); 59 | (void)fmpool_get(Point_t,pool); 60 | fmpool_free(Point_t,p,pool); 61 | return(p == fmpool_get(Point_t,pool)); 62 | } 63 | 64 | int main() 65 | { 66 | zero_allocation_test("Allocating zero-length pool isn't allowed"); 67 | allocate_test(1,one_allocation_test, "Failed allocating one-length pool"); 68 | allocate_test(2,freelist_simple_test,"Failed freelist simple test"); 69 | free_under_and_overflow_test("Failed under/over flow test"); 70 | return 0; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /test/perftest/Makefile: -------------------------------------------------------------------------------- 1 | NAME = perftest 2 | 3 | OBJDIR = ../../bin 4 | 5 | CFLAGS = 6 | CFLAGS += -g 7 | CFLAGS += -O3 8 | CFLAGS += -Wall 9 | CFLAGS += --std=c99 10 | CFLAGS += -Wno-unused-function 11 | CFLAGS += -fno-inline 12 | CFLAGS += -I../.. 13 | 14 | all: 15 | $(CC) $(CFLAGS) $(NAME).c -o $(OBJDIR)/$(NAME) 16 | 17 | clean: 18 | rm -fr $(OBJDIR)/$(NAME).o $(OBJDIR)/$(NAME) $(OBJDIR)/$(NAME).dSYM 19 | -------------------------------------------------------------------------------- /test/perftest/perftest.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "fmpool.h" 5 | 6 | #define NOINLINE __attribute__((noinline)) 7 | 8 | typedef struct Point_s 9 | { 10 | double x; 11 | double y; 12 | } Point_t; 13 | 14 | FMPOOL_INIT(Point_t) 15 | 16 | static NOINLINE void* pool_test_func(const size_t num) 17 | { 18 | void* save = NULL; 19 | fmpool_t(Point_t)* P; 20 | P = fmpool_create(Point_t, num); 21 | for(size_t i = 0; i < num; i++) 22 | { 23 | Point_t* item = fmpool_get(Point_t, P); 24 | if(item) 25 | { 26 | save = item; 27 | } 28 | else 29 | { 30 | perror("pool malloc"); 31 | exit(1); 32 | } 33 | } 34 | fmpool_destroy(Point_t, P); 35 | return save; 36 | } 37 | 38 | static NOINLINE void* malloc_test_func(const size_t num) 39 | { 40 | void* save = NULL; 41 | for(size_t i = 0; i < num; i++) 42 | { 43 | Point_t* item; 44 | item = malloc(sizeof(Point_t)); 45 | if(item) 46 | { 47 | save = item; 48 | } 49 | else 50 | { 51 | perror("regular malloc"); 52 | exit(1); 53 | } 54 | free(item); 55 | } 56 | return save; 57 | } 58 | 59 | /* Signature of a function that takes a size_t and returns a void pointer */ 60 | typedef void* TEST_FUNC_SIG (const size_t); 61 | 62 | static NOINLINE void Test(TEST_FUNC_SIG* func, char* text) 63 | { 64 | clock_t begin, end; 65 | double ms_elapsed; 66 | void* res = NULL; 67 | for(size_t i = 1000; i < 1000000; i*=10) 68 | { 69 | begin = clock(); 70 | res = func(i); 71 | end = clock(); 72 | 73 | ms_elapsed = (double)(end - begin) / CLOCKS_PER_SEC / 0.001; 74 | 75 | printf("%f ms:\t%s time to create and destroy %lu objects. ", 76 | ms_elapsed, text, i); 77 | printf("last pointer: %p", res); 78 | printf("\n"); 79 | } 80 | } 81 | 82 | static NOINLINE void DoTests(void) 83 | { 84 | TEST_FUNC_SIG* func; 85 | 86 | func = &pool_test_func; 87 | Test(func, "FMPool"); 88 | 89 | func = &malloc_test_func; 90 | Test(func, "Malloc"); 91 | } 92 | 93 | int main() 94 | { 95 | DoTests(); 96 | 97 | return 0; 98 | } 99 | 100 | --------------------------------------------------------------------------------