Pool allocator
This commit is contained in:
parent
59fee5094b
commit
c2fe01f04c
|
@ -4,7 +4,8 @@ My personal library of common C data structures and algorithms. Supports Linux,
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Extensive documentation can be found [here](https://fputs.com/docs/libflint/)
|
Extensive documentation can be found [here](https://fputs.com/docs/libflint/). You can also check out `tests/tests.c` to
|
||||||
|
see example usage from most of the library's API.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Custom allocators and memory functions
|
Custom allocators and memory functions
|
||||||
|
|
||||||
# Arena Allocator
|
## Arena Allocator
|
||||||
|
|
||||||
A simple arena-style allocator
|
A simple arena-style allocator
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ A simple arena-style allocator
|
||||||
### ArenaAllocator
|
### ArenaAllocator
|
||||||
|
|
||||||
Represents an arena allocator. `ArenaAllocator` holds its own buffer, but managing its size is left to the user. Like
|
Represents an arena allocator. `ArenaAllocator` holds its own buffer, but managing its size is left to the user. Like
|
||||||
most structs in `libflint`, it must be malloced first before being passed to `arena_init()`.
|
most structs in `libflint`, it must be malloc'd first before being passed to `arena_init()`.
|
||||||
|
|
||||||
```c
|
```c
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -97,3 +97,98 @@ Convenience macro for getting the size of the arena's buffer
|
||||||
```c
|
```c
|
||||||
#define arena_sz(a) (a)->buf_sz
|
#define arena_sz(a) (a)->buf_sz
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Pool Allocator
|
||||||
|
|
||||||
|
A pool-based allocator for chunks of the same size. Useful for quickly allocating and using memory of the same size
|
||||||
|
without worrying about order; most operations are `O(1)`
|
||||||
|
|
||||||
|
## Structs
|
||||||
|
|
||||||
|
### PoolAllocator
|
||||||
|
|
||||||
|
Represents a pool allocator. `PoolAllocator` holds its own buffer, but managing its size is left to the user. Like
|
||||||
|
most structs in `libflint`, it must be malloc'd first before being passed to `pool_init()`.
|
||||||
|
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
unsigned char *buf;
|
||||||
|
size_t buf_sz;
|
||||||
|
size_t chunk_size;
|
||||||
|
|
||||||
|
List *free_list;
|
||||||
|
} PoolAllocator;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
### pool_init
|
||||||
|
|
||||||
|
Initializes the `PoolAllocator`. The struct must first be created by the user using `malloc()`, see the example below.
|
||||||
|
|
||||||
|
- `buf_sz`: Size of the underlying buffer in bytes
|
||||||
|
- `chunk_sz`: Size of each chunk in bytes
|
||||||
|
- `chunk_align`: The alignment of the chunks. The`LF_DEFAULT_ALIGNMENT` macro is available as a good default for basic types
|
||||||
|
|
||||||
|
```c
|
||||||
|
void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align);
|
||||||
|
|
||||||
|
/* Usage */
|
||||||
|
PoolAllocator *pool = malloc(sizeof(PoolAllocator));
|
||||||
|
pool_init(pool, 64, 16, LF_DEFAULT_ALIGNMENT);
|
||||||
|
```
|
||||||
|
|
||||||
|
### pool_free
|
||||||
|
|
||||||
|
Return a single chunk back to the pool. `ptr` is a pointer to the allocated chunk.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void pool_free(PoolAllocator *allocator, void *ptr);
|
||||||
|
```
|
||||||
|
|
||||||
|
### pool_free_all
|
||||||
|
|
||||||
|
Returns all chunks back to the pool. Any pointers received before this call are now invalid and must be reasigned with
|
||||||
|
`pool_alloc` or set to `NULL`.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void pool_free_all(PoolAllocator *allocator);
|
||||||
|
```
|
||||||
|
|
||||||
|
### pool_alloc
|
||||||
|
|
||||||
|
Allocate a chunk from the pool. Returns `NULL` on failure.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void *pool_alloc(PoolAllocator *allocator);
|
||||||
|
```
|
||||||
|
|
||||||
|
### pool_destroy
|
||||||
|
|
||||||
|
Destroys the underlying buffer and `List` in `allocator`, then frees it. User is responsible for setting `allocator` to
|
||||||
|
`NULL` afterwards.
|
||||||
|
|
||||||
|
```c
|
||||||
|
void pool_destroy(PoolAllocator *allocator);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Macros
|
||||||
|
|
||||||
|
### pool_count_available
|
||||||
|
|
||||||
|
Returns the amount of chunks left available in the pool
|
||||||
|
|
||||||
|
```c
|
||||||
|
#define pool_count_available(x) (x)->free_list->size
|
||||||
|
```
|
||||||
|
|
||||||
|
### LF_DEFAULT_ALIGNMENT
|
||||||
|
|
||||||
|
The default alignment for allocators. Use this as a simple default (defaults to 16 bytes on amd64 systems) when storing
|
||||||
|
basic types
|
||||||
|
|
||||||
|
```c
|
||||||
|
#ifndef DEFAULT_ALIGNMENT
|
||||||
|
#define LF_DEFAULT_ALIGNMENT (2*sizeof(void*))
|
||||||
|
#endif // DEFAULT_ALIGNMENT
|
||||||
|
```
|
|
@ -3,6 +3,12 @@
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "lflinkedlist.h"
|
||||||
|
|
||||||
|
#ifndef DEFAULT_ALIGNMENT
|
||||||
|
#define LF_DEFAULT_ALIGNMENT (2*sizeof(void*))
|
||||||
|
#endif // DEFAULT_ALIGNMENT
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
unsigned char* buf;
|
unsigned char* buf;
|
||||||
size_t buf_sz;
|
size_t buf_sz;
|
||||||
|
@ -17,4 +23,20 @@ void arena_resize_buf(ArenaAllocator *allocator, size_t new_sz);
|
||||||
void *arena_resize(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz);
|
void *arena_resize(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz);
|
||||||
void arena_clear(ArenaAllocator *allocator);
|
void arena_clear(ArenaAllocator *allocator);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char *buf;
|
||||||
|
size_t buf_sz;
|
||||||
|
size_t chunk_size;
|
||||||
|
|
||||||
|
List *free_list;
|
||||||
|
} PoolAllocator;
|
||||||
|
|
||||||
|
void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align);
|
||||||
|
void pool_free(PoolAllocator *allocator, void *ptr);
|
||||||
|
void pool_free_all(PoolAllocator *allocator);
|
||||||
|
void *pool_alloc(PoolAllocator *allocator);
|
||||||
|
void pool_destroy(PoolAllocator *allocator);
|
||||||
|
|
||||||
|
#define pool_count_available(x) (x)->free_list->size
|
||||||
|
|
||||||
#endif // LIBFLINT_H_MEMORY
|
#endif // LIBFLINT_H_MEMORY
|
||||||
|
|
|
@ -12,6 +12,7 @@ nav:
|
||||||
- 'Linked List': 'linkedlist.md'
|
- 'Linked List': 'linkedlist.md'
|
||||||
- 'Math': 'math.md'
|
- 'Math': 'math.md'
|
||||||
- 'macOS': 'macos.md'
|
- 'macOS': 'macos.md'
|
||||||
|
- 'Memory': 'memory.md'
|
||||||
- 'Network': 'network.md'
|
- 'Network': 'network.md'
|
||||||
- 'Parsing': 'parsing.md'
|
- 'Parsing': 'parsing.md'
|
||||||
- 'Set': 'set.md'
|
- 'Set': 'set.md'
|
||||||
|
|
111
src/memory.c
111
src/memory.c
|
@ -28,26 +28,37 @@ void arena_clear(ArenaAllocator *allocator) {
|
||||||
allocator->offset_prev = 0;
|
allocator->offset_prev = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static uintptr_t align_forward(uintptr_t ptr, size_t align) {
|
static uintptr_t align_forward_uintptr(const uintptr_t ptr, const uintptr_t align) {
|
||||||
uintptr_t p, a, m;
|
|
||||||
if (!is_power_of_two(align)) {
|
if (!is_power_of_two(align)) {
|
||||||
// TODO: Error
|
// TODO: Error
|
||||||
}
|
}
|
||||||
p = ptr;
|
uintptr_t p = ptr;
|
||||||
a = (uintptr_t)align;
|
const uintptr_t m = p & (align - 1);
|
||||||
m = p & (a - 1);
|
|
||||||
|
|
||||||
if (m != 0) {
|
if (m != 0) {
|
||||||
p += a - m;
|
p += align - m;
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *arena_malloc_align(ArenaAllocator *allocator, size_t size, size_t align) {
|
static uintptr_t align_forward_size(const size_t ptr, const size_t align) {
|
||||||
uintptr_t cur_ptr = (uintptr_t)allocator->buf + (uintptr_t)allocator->offset_cur;
|
if (!is_power_of_two(align)) {
|
||||||
|
// TODO: Error
|
||||||
|
}
|
||||||
|
uintptr_t p = ptr;
|
||||||
|
const uintptr_t m = p & (align - 1);
|
||||||
|
|
||||||
|
if (m != 0) {
|
||||||
|
p += align - m;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *arena_malloc_align(ArenaAllocator *allocator, const size_t size, size_t align) {
|
||||||
|
uintptr_t cur_ptr = (uintptr_t)allocator->buf + allocator->offset_cur;
|
||||||
|
|
||||||
// Push forward to align, then change to relative offset
|
// Push forward to align, then change to relative offset
|
||||||
uintptr_t offset = align_forward(cur_ptr, align);
|
uintptr_t offset = align_forward_uintptr(cur_ptr, align);
|
||||||
offset -= (uintptr_t)allocator->buf;
|
offset -= (uintptr_t)allocator->buf;
|
||||||
|
|
||||||
if (offset + size <= allocator->buf_sz) {
|
if (offset + size <= allocator->buf_sz) {
|
||||||
|
@ -62,8 +73,8 @@ static void *arena_malloc_align(ArenaAllocator *allocator, size_t size, size_t a
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void *arena_resize_align(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz, size_t align) {
|
static void *arena_resize_align(ArenaAllocator *allocator, void *mem, const size_t old_sz, const size_t new_sz, size_t align) {
|
||||||
unsigned char *old_mem = (unsigned char *)mem;
|
unsigned char *old_mem = mem;
|
||||||
if (!is_power_of_two(align)) {
|
if (!is_power_of_two(align)) {
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
}
|
}
|
||||||
|
@ -91,19 +102,79 @@ static void *arena_resize_align(ArenaAllocator *allocator, void *mem, size_t old
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void arena_resize_buf(ArenaAllocator *allocator, size_t new_sz) {
|
void arena_resize_buf(ArenaAllocator *allocator, const size_t new_sz) {
|
||||||
allocator->buf = realloc(allocator->buf, sizeof(unsigned char) * new_sz);
|
allocator->buf = realloc(allocator->buf, sizeof(unsigned char) * new_sz);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef DEFAULT_ALIGNMENT
|
void *arena_malloc(ArenaAllocator *allocator, const size_t size) {
|
||||||
#define DEFAULT_ALIGNMENT (2*sizeof(void*))
|
return arena_malloc_align(allocator, size, LF_DEFAULT_ALIGNMENT);
|
||||||
#endif // DEFAULT_ALIGNMENT
|
|
||||||
|
|
||||||
void *arena_malloc(ArenaAllocator *allocator, size_t size) {
|
|
||||||
return arena_malloc_align(allocator, size, DEFAULT_ALIGNMENT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void *arena_resize(ArenaAllocator *allocator, void *mem, size_t old_sz, size_t new_sz) {
|
void *arena_resize(ArenaAllocator *allocator, void *mem, const size_t old_sz, const size_t new_sz) {
|
||||||
return arena_resize_align(allocator, mem, old_sz, new_sz, DEFAULT_ALIGNMENT);
|
return arena_resize_align(allocator, mem, old_sz, new_sz, LF_DEFAULT_ALIGNMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pool_init(PoolAllocator *allocator, size_t buf_sz, size_t chunk_sz, size_t chunk_align) {
|
||||||
|
if (allocator == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocator->buf = malloc(sizeof(unsigned char) * buf_sz);
|
||||||
|
uintptr_t istart = (uintptr_t)allocator->buf;
|
||||||
|
uintptr_t start = align_forward_uintptr(istart, chunk_align);
|
||||||
|
allocator->buf_sz = buf_sz - (start - istart);
|
||||||
|
|
||||||
|
allocator->chunk_size = align_forward_size(chunk_sz, chunk_align);
|
||||||
|
if (allocator->chunk_size < sizeof(void *) || allocator->buf_sz < allocator->chunk_size) {
|
||||||
|
//TODO: Handle error better
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocator->free_list = malloc(sizeof(List));
|
||||||
|
ll_init(allocator->free_list, NULL);
|
||||||
|
|
||||||
|
pool_free_all(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pool_free(PoolAllocator *allocator, void *ptr) {
|
||||||
|
ListNode *node = NULL;
|
||||||
|
const void *start = allocator->buf;
|
||||||
|
const void *end = &allocator->buf[allocator->buf_sz];
|
||||||
|
|
||||||
|
if (ptr == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(start <= ptr && ptr < end)) {
|
||||||
|
// TODO: Handle error better
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ll_ins_next(allocator->free_list, allocator->free_list->tail, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pool_free_all(PoolAllocator *allocator) {
|
||||||
|
size_t chunk_count = allocator->buf_sz / allocator->chunk_size;
|
||||||
|
for (size_t i = 0; i < chunk_count; ++i) {
|
||||||
|
ll_ins_next(allocator->free_list, allocator->free_list->head, &allocator->buf[i * allocator->chunk_size]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void *pool_alloc(PoolAllocator *allocator) {
|
||||||
|
ListNode *node = allocator->free_list->head;
|
||||||
|
if (node == NULL) {
|
||||||
|
// TODO: Handle error better
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *tmp;
|
||||||
|
ll_remove(allocator->free_list, allocator->free_list->head, &tmp);
|
||||||
|
return memset(tmp, 0, allocator->chunk_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pool_destroy(PoolAllocator *allocator) {
|
||||||
|
ll_destroy(allocator->free_list);
|
||||||
|
free(allocator->free_list);
|
||||||
|
free(allocator->buf);
|
||||||
|
free(allocator);
|
||||||
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ int vec_shrink(Vector *vec) {
|
||||||
|
|
||||||
vec->capacity = vec_len(vec);
|
vec->capacity = vec_len(vec);
|
||||||
|
|
||||||
#ifdef __OpenBSD__
|
#if !defined(__OpenBSD__) || defined(__linux)
|
||||||
vec->elements = reallocarray(vec->elements, vec->capacity, sizeof(void *));
|
vec->elements = reallocarray(vec->elements, vec->capacity, sizeof(void *));
|
||||||
#else
|
#else
|
||||||
vec->elements = reallocf(vec->elements, sizeof(void *) * vec->capacity);
|
vec->elements = reallocf(vec->elements, sizeof(void *) * vec->capacity);
|
||||||
|
|
|
@ -471,11 +471,11 @@ void test_macos() {
|
||||||
|
|
||||||
void test_memory() {
|
void test_memory() {
|
||||||
printf("\n--- MEMORY TEST ---\n");
|
printf("\n--- MEMORY TEST ---\n");
|
||||||
ArenaAllocator *a = malloc(sizeof(ArenaAllocator));
|
ArenaAllocator *arena = malloc(sizeof(ArenaAllocator));
|
||||||
arena_init(a, 1024);
|
arena_init(arena, 1024);
|
||||||
|
|
||||||
int *i1 = arena_malloc(a, sizeof(int));
|
int *i1 = arena_malloc(arena, sizeof(int));
|
||||||
int *i2 = arena_malloc(a, sizeof(int));
|
int *i2 = arena_malloc(arena, sizeof(int));
|
||||||
|
|
||||||
*i1 = 1;
|
*i1 = 1;
|
||||||
*i2 = 2;
|
*i2 = 2;
|
||||||
|
@ -483,13 +483,33 @@ void test_memory() {
|
||||||
assert(i1 < i2);
|
assert(i1 < i2);
|
||||||
assert(*i1 < *i2);
|
assert(*i1 < *i2);
|
||||||
|
|
||||||
long *l = arena_resize(a, i1, sizeof(int), sizeof(long));
|
long *l = arena_resize(arena, i1, sizeof(int), sizeof(long));
|
||||||
assert(*l == 1);
|
assert(*l == 1);
|
||||||
|
|
||||||
unsigned char *c = arena_resize(a, i2, sizeof(int), sizeof(unsigned char));
|
unsigned char *char_test = arena_resize(arena, i2, sizeof(int), sizeof(unsigned char));
|
||||||
assert(*c == 2);
|
assert(*char_test == 2);
|
||||||
|
|
||||||
arena_free(a);
|
arena_free(arena);
|
||||||
|
arena = NULL;
|
||||||
|
|
||||||
|
PoolAllocator *pool = malloc(sizeof(PoolAllocator));
|
||||||
|
pool_init(pool, 64, 16, LF_DEFAULT_ALIGNMENT);
|
||||||
|
void *a = pool_alloc(pool);
|
||||||
|
void *b = pool_alloc(pool);
|
||||||
|
void *c = pool_alloc(pool);
|
||||||
|
void *d = pool_alloc(pool);
|
||||||
|
|
||||||
|
assert(a != NULL);
|
||||||
|
assert(b != NULL);
|
||||||
|
assert(c != NULL);
|
||||||
|
assert(d != NULL);
|
||||||
|
|
||||||
|
assert(pool_count_available(pool) == 0);
|
||||||
|
pool_free(pool, d);
|
||||||
|
d = NULL;
|
||||||
|
assert(pool_count_available(pool) == 1);
|
||||||
|
|
||||||
|
pool_destroy(pool);
|
||||||
printf("Passes all memory tests\n");
|
printf("Passes all memory tests\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue