Pool allocator
Test and Deploy / test (push) Successful in 15s Details
Test and Deploy / docs (push) Successful in 16s Details

This commit is contained in:
Evan Burkey 2024-07-16 21:52:02 -07:00
parent 59fee5094b
commit fe443f0deb
6 changed files with 240 additions and 31 deletions

View File

@ -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 {
@ -96,4 +96,99 @@ 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
``` ```

View File

@ -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

View File

@ -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'

View File

@ -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);
}

View File

@ -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);

View File

@ -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");
} }