forked from amir/filehasher
Small improvements of the LF MPMC queue Making the LF MPMC queue generic and in a seperate header file
382 lines
12 KiB
C
382 lines
12 KiB
C
#pragma once
|
|
|
|
#include "base.h"
|
|
|
|
/*
|
|
===============================================================================
|
|
ARENA USAGE GUIDE
|
|
===============================================================================
|
|
|
|
OVERVIEW
|
|
--------
|
|
The arena allocator is a high-performance memory allocator designed for
|
|
predictable allocation patterns. It supports:
|
|
|
|
- Multiple chained memory blocks
|
|
- Pointer-style allocation with free-list reuse
|
|
- Stack-style allocation
|
|
- Optional Swap-back removal for unordered data
|
|
- Merging compatible arenas
|
|
|
|
Memory is committed in page-sized chunks. The arena never commits less than
|
|
the system page size and grows by allocating new blocks when needed.
|
|
|
|
|
|
CORE CONCEPTS
|
|
-------------
|
|
|
|
Arena Blocks
|
|
~~~~~~~~~~~~
|
|
An arena consists of one or more memory blocks linked together.
|
|
|
|
Each block contains:
|
|
- base_pos : The local starting position of a block (bytes)
|
|
- pos : Global position (bytes)
|
|
- commit_size : Total committed size (bytes)
|
|
- reserve_size : Total usable size of the block (bytes)
|
|
- prev/next: Links to neighboring blocks
|
|
- the arena pointer points to the current block
|
|
|
|
The arena allocates from the current block where the global position is or from
|
|
the free list.
|
|
|
|
Blocks form a single logical address space:
|
|
global_offset = pos
|
|
|
|
|
|
Global Position Model
|
|
~~~~~~~~~~~~~~~~~~~~~
|
|
All allocations are addressed using a global offset. This allows:
|
|
- Popping across block boundaries
|
|
- Cross-block swap-back operations
|
|
- Arena merging by linking blocks
|
|
|
|
|
|
ARENA CONFIGURATION
|
|
-------------------
|
|
|
|
An arena is configured usings the struct arena_params before creation.
|
|
|
|
Important parameters:
|
|
- push_size : Fixed element size in bytes (0 = variable-size arena)
|
|
- allow_free_list : Enables arena_free() and pointer mode, disabling it will
|
|
enable stack mode
|
|
- allow_swapback : Enables arena_swapback_pop(), works in stack mode only
|
|
- max_nbre_blocks : Maximum number of blocks (0 = unlimited)
|
|
- growth_policy : How blocks grow (next created block will have the same size
|
|
or double the size)
|
|
- commit_policy : How memory is committed (lazily if needed or commit all at
|
|
block creation)
|
|
|
|
|
|
ARENA CREATION AND DESTRUCTION
|
|
------------------------------
|
|
|
|
We create arenas using
|
|
mem_arena *arena_create(arena_params *params);
|
|
|
|
Behavior:
|
|
- Create an arena and return the pointer to this arena or NULL if it fails
|
|
|
|
Requirements:
|
|
- The configuration of the arena needs to be injected as an argument using
|
|
arena_params
|
|
|
|
We destroy arenas using
|
|
void *arena_destroy(mem_arena **arena_ptr);
|
|
|
|
Behavior:
|
|
- Return (void *)1 if it succeeds or NULL if it fails
|
|
|
|
|
|
ALLOCATION (arena_push)
|
|
-----------------------
|
|
|
|
We allocate using one function:
|
|
void *arena_push(mem_arena **arena_ptr, u64 size, bool non_zero);
|
|
|
|
Behavior:
|
|
- zero can be set to true of false to zero the allocated memory or not
|
|
- If the current block is full and block_index < max_nbre_blocks, Grows into a
|
|
new block, setting the new block to arena->next then setting the arena pointer
|
|
to the new block
|
|
- The size can be set only if the arena has variable-size elements (push_size ==
|
|
0)
|
|
- Return the pointer of the pushed element if it succeeds or NULL if it fails
|
|
|
|
Variable-size allocation (allow_free_list == true, pointer mode only):
|
|
|
|
- Allocates 'size' bytes
|
|
- Only available in pointer mode with a free list
|
|
- Since the size of the elements is variable, when using the free list we loop
|
|
through all the elements until finding one with enough size, after allocation if
|
|
there is a remaining we store it as a new entry in the free list. The allocation
|
|
is slower than fixed-size arenas
|
|
- If there is not enough memory to push an element we try to create a new block
|
|
and push the element to it, if there is some memory remaining in the previous
|
|
block we add it to the free list
|
|
- max_nbre_blocks determines the behavior of the free list
|
|
- If max_nbre_blocks > 0 we push directly to the arena until it's full then
|
|
we use the free list
|
|
- If max_nbre_blocks = 0 (unlimited number of blocks) we use the free list
|
|
directly
|
|
|
|
Fixed-size allocation (allow_free_list can be true of false):
|
|
|
|
- Size is ignored, push_size defines element size (arena config)
|
|
- Available for both in stack and pointer modes
|
|
- Caller-provided size is ignored
|
|
- Required for swap-back correctness
|
|
- Faster and safer than variable-size mode
|
|
- If allow_free_list == true (pointer mode), uses the free list first then push
|
|
|
|
DEALLOCATION
|
|
------------
|
|
POINTER MODE (WITH A FREE LIST)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
- Popping requires the pointer that points to the element
|
|
- The only function allowed to pop is arena_free()
|
|
|
|
void *arena_free(mem_arena **arena_ptr, u8 **ptr, u64 size);
|
|
|
|
Behavior:
|
|
- Stores (offset, size) in the free list
|
|
- Memory is reused on future allocations
|
|
- Sets the pointer to NULL
|
|
- The freed memory is not zeroed, we can zero the memory only when allocating
|
|
- If the element is last, behaves like a pop, decrementing pos, if the position
|
|
crosses to the previous arena block, set the arena pointer to arena->prev
|
|
- Return (void *)1 if it succeeds or NULL if it fails
|
|
|
|
Requirements:
|
|
- allow_free_list == true
|
|
- Correct element size for variable-size arenas
|
|
|
|
STACK MODE (HAS NO FREE LIST)
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
- If allow_swapback = false, we can only pop with arena_pop_to()
|
|
- If allow_swapback = true, we can pop with arena_pop_to() and
|
|
arena_swapback_pop()
|
|
|
|
Pop to position:
|
|
void *arena_pop_to(mem_arena **arena_ptr, u64 count);
|
|
|
|
- Removes x elements from the top of the stack
|
|
- Can remove data across blocks
|
|
- Pops the element by moving the position (pos)
|
|
- Does not zero memory we can zero the memory only when allocating
|
|
- If we pop to the previous arena block, set the arena pointer to arena->prev
|
|
- Return (void *)1 if it succeeds or NULL if it fails
|
|
|
|
Requirements:
|
|
- allow_free_list == false
|
|
- Fixed-size elements (push_size > 0)
|
|
- The number of elements to pop
|
|
|
|
A macro arena_pop(arena) is available to pop one element
|
|
|
|
void *arena_swapback_pop(mem_arena **arena_ptr, u64 index);
|
|
|
|
- The top of the stack element is copied into the removed slot, the top of the
|
|
stack is then reduced by one element
|
|
- Works across multiple blocks
|
|
- Return (void *)1 if it succeeds or NULL if it fails
|
|
- Swapping back the last element will only pop it
|
|
|
|
Cases:
|
|
- Middle of block -> swapback
|
|
- End of non-last block -> swapback
|
|
- Last element overall -> pop only
|
|
|
|
Requirements:
|
|
- allow_free_list == false
|
|
- Fixed-size elements (push_size > 0)
|
|
- allow_swapback == true
|
|
|
|
|
|
CLEARING THE ARENA
|
|
------------------
|
|
|
|
void *arena_clear(mem_arena **arena_ptr);
|
|
|
|
- Resets pos for all blocks
|
|
- Keeps all committed memory
|
|
- Does NOT zero memory
|
|
- Resets the arena pointer to the first arena block
|
|
- Resets the free-list
|
|
- Return (void *)1 if it succeeds or NULL if it fails
|
|
|
|
|
|
MERGING ARENAS
|
|
--------------
|
|
|
|
mem_arena *arena_merge(mem_arena **dst_ptr, mem_arena **src_ptr);
|
|
|
|
Behavior:
|
|
Merges arena B into arena A by:
|
|
- Updating the pos and base_pos and block_index of all the blocks of B
|
|
- Linking the two arenas by setting arena->prev of the first block of B to the
|
|
last block of A
|
|
- If there is empty blocks in arena A, append them to the end of arena B
|
|
- Resets the arena pointer to arena B
|
|
- Merges the free list arenas if available
|
|
- max_nbre_blocks is summed
|
|
- Return the pointer to the new arena of NULL if it fails
|
|
|
|
Requirements:
|
|
- Configurations must match exactly
|
|
|
|
HELPER FUNCTIONS
|
|
------------------
|
|
|
|
mem_arena *arena_block_from_pos(mem_arena *last, u64 global_pos);
|
|
mem_arena *arena_block_from_index(mem_arena *last, u64 index);
|
|
mem_arena *arena_block_from_ptr(mem_arena *last, u8 *ptr);
|
|
u64 arena_pos_from_ptr(mem_arena *arena, void *ptr);
|
|
void *arena_ptr_from_pos(mem_arena *arena, u64 global_pos);
|
|
void *arena_ptr_from_index(mem_arena *arena, u64 index);
|
|
===============================================================================
|
|
*/
|
|
|
|
#define ARENA_HEADER_SIZE (sizeof(mem_arena))
|
|
#define ARENA_ALIGN (sizeof(void *))
|
|
|
|
// arena config
|
|
typedef enum arena_growth_policy {
|
|
ARENA_GROWTH_NORMAL = 0, // grow by fixed block size
|
|
ARENA_GROWTH_DOUBLE // double block size on each growth
|
|
} arena_growth_policy;
|
|
|
|
typedef enum arena_commit_policy {
|
|
ARENA_COMMIT_LAZY = 0, // commit pages on demand
|
|
ARENA_COMMIT_FULL // commit entire reserve at creation
|
|
} arena_commit_policy;
|
|
|
|
typedef struct arena_params {
|
|
u64 reserve_size; // size of one arena block
|
|
u64 commit_size; // initial commit size
|
|
u64 align; // allocation alignment, 0 to disable and ARENA_ALIGN to align
|
|
// according to architecture
|
|
|
|
// Element size rules:
|
|
// - stack mode : push_size > 0 (mandatory)
|
|
// - pointer fixed : push_size > 0
|
|
// - pointer variable : push_size == 0
|
|
u64 push_size;
|
|
|
|
struct mem_arena *free_list;
|
|
b32 allow_free_list; // pointer mode if true
|
|
|
|
b32 allow_swapback; // stack mode only
|
|
|
|
arena_growth_policy growth_policy;
|
|
arena_commit_policy commit_policy;
|
|
|
|
u32 max_nbre_blocks; // 0 = unlimited
|
|
} arena_params;
|
|
|
|
typedef struct arena_free_node {
|
|
u64 offset; // offset from arena base
|
|
u64 size; // size of freed block
|
|
} arena_free_node;
|
|
|
|
// arena definition struct
|
|
typedef struct mem_arena {
|
|
// block chaining
|
|
struct mem_arena *prev;
|
|
struct mem_arena *next; // valid only on root arena
|
|
|
|
// positions
|
|
u64 base_pos; // of selected block
|
|
u64 pos; // global pos
|
|
|
|
// memory limits
|
|
u64 reserve_size;
|
|
u64 commit_size;
|
|
u64 commit_pos;
|
|
|
|
// configuration
|
|
u64 align;
|
|
u64 push_size;
|
|
|
|
// Pointer mode only
|
|
struct mem_arena *free_list;
|
|
b32 allow_free_list;
|
|
|
|
// Stack mode only
|
|
b32 allow_swapback;
|
|
|
|
arena_growth_policy growth_policy;
|
|
arena_commit_policy commit_policy;
|
|
u32 max_nbre_blocks;
|
|
u32 block_index;
|
|
|
|
} mem_arena;
|
|
|
|
typedef struct mem_arena_temp {
|
|
mem_arena *arena;
|
|
u64 pos;
|
|
} mem_arena_temp;
|
|
|
|
// helper functions
|
|
mem_arena *arena_block_from_pos(mem_arena *last, u64 global_pos);
|
|
mem_arena *arena_block_from_index(mem_arena *last, u64 index);
|
|
mem_arena *arena_block_from_ptr(mem_arena *last, u8 *ptr);
|
|
u64 arena_pos_from_ptr(mem_arena *arena, void *ptr);
|
|
void *arena_ptr_from_pos(mem_arena *arena, u64 global_pos);
|
|
void *arena_ptr_from_index(mem_arena *arena, u64 index);
|
|
|
|
// arena core functions
|
|
// creation / destruction
|
|
mem_arena *arena_create(arena_params *params);
|
|
void *arena_destroy(mem_arena **arena_ptr);
|
|
|
|
// allocation
|
|
void *arena_push(mem_arena **arena_ptr, u64 size, bool non_zero);
|
|
|
|
// pointer mode only
|
|
// - fixed-size arena : size is ignored
|
|
// - variable-size arena: size is mandatory
|
|
void *arena_free(mem_arena **arena_ptr, u8 **ptr, u64 size);
|
|
|
|
// stack mode only
|
|
void *arena_pop_to(mem_arena **arena_ptr, u64 count);
|
|
void *arena_swapback_pop(mem_arena **arena_ptr, u64 index);
|
|
|
|
// utilities
|
|
void *arena_clear(mem_arena **arena_ptr);
|
|
|
|
mem_arena *arena_merge(mem_arena **dst_ptr, mem_arena **src_ptr);
|
|
|
|
// temp arenas
|
|
/* create a temporary arena on top of an existing arena, it:
|
|
* takes an existing arena, marks it's position, allocate beyond this position
|
|
* and free it until the marked position when not needed*/
|
|
mem_arena_temp arena_temp_begin(mem_arena *arena);
|
|
void arena_temp_end(mem_arena_temp temp);
|
|
|
|
mem_arena_temp arena_scratch_get(mem_arena **conflicts, u32 num_conflicts);
|
|
void arena_scratch_release(mem_arena_temp scratch);
|
|
|
|
#if defined(_MSC_VER)
|
|
#define THREAD_LOCAL __declspec(thread)
|
|
#else
|
|
#define THREAD_LOCAL __thread
|
|
#endif
|
|
|
|
// Helpers
|
|
// Pointer mode only
|
|
/* Fixed-size arena: size is implicit, can pass 0 */
|
|
#define PUSH_STRUCT(arena, T) \
|
|
((T *)arena_push((arena), 0, true)) // Zeroes the memory
|
|
#define PUSH_STRUCT_NZ(arena, T) ((T *)arena_push((arena), 0, false))
|
|
|
|
#define PUSH_ARRAY(arena, T, n) ((T *)arena_push((arena), 0, true))
|
|
#define PUSH_ARRAY_NZ(arena, T, n) ((T *)arena_push((arena), 0, false))
|
|
|
|
/* Variable-size arena helpers: REQUIRE explicit size via arena_push() */
|
|
#define ARENA_PUSH(arena, size) arena_push((arena), (size), true)
|
|
#define ARENA_PUSH_NZ(arena, size) arena_push((arena), (size), false)
|
|
|
|
#define arena_pop(arena_ptr) arena_pop_to((arena_ptr), 1)
|