facil.io

#Memory Allocator

c
#define FIO_MEMORY_NAME fio
#include "fio-stl.h"

A concurrent, per-arena memory allocator that groups objects with similar lifetimes. It tries to keep related allocations in the same chunk so cleanup is cheap and locality is decent. Define FIO_MEMORY_DISABLE to route everything to the system malloc/free instead.

The prefix fio comes from FIO_MEMORY_NAME. All public function names are built with FIO_NAME(FIO_MEMORY_NAME, ...), so a custom prefix produces my_malloc, my_free, and so on.

The module also sets the temporary allocator macros (FIO_MEM_REALLOC_, FIO_MEM_FREE_, FIO_MEM_REALLOC_IS_SAFE_, FIO_MEM_ALIGNMENT_SIZE_) for any facil.io type defined in the same include scope.

Note: FIO_MALLOC is a shortcut that defines this allocator with prefix fio and general-purpose tuning.


#Core API

#fio_malloc

c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, malloc)(size_t size);

Allocates size bytes aligned to FIO_MEMORY_ALIGN_SIZE. Memory is zeroed if FIO_MEMORY_INITIALIZE_ALLOCATIONS is enabled. Allocations above the arena limit use big-block mode or mmap.

Parameters:

  • size — bytes to allocate.

Returns: pointer to allocated memory, or NULL on failure.

#fio_calloc

c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME,
                                       calloc)(size_t size_per_unit,
                                               size_t unit_count);

Equivalent to fio_malloc(size_per_unit * unit_count).

#fio_realloc

c
SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc)(void *ptr,
                                                             size_t new_size);

Reallocates ptr to new_size. Copy-free expansion is only attempted for large allocations. Prefer fio_realloc2 for predictable zeroing.

Parameters:

  • ptr — existing allocation, or NULL.
  • new_size — requested new size.

Returns: pointer to the new block, or NULL on failure.

#fio_realloc2

c
SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME, realloc2)(void *ptr,
                                                              size_t new_size,
                                                              size_t copy_len);

Reallocates ptr to new_size, copying at most copy_len bytes. Memory beyond copy_len is zeroed when FIO_MEMORY_INITIALIZE_ALLOCATIONS is enabled.

Parameters:

  • ptr — existing allocation, or NULL.
  • new_size — requested new size.
  • copy_len — maximum bytes to copy from the old block.

Returns: pointer to the new block, or NULL on failure.

#fio_free

c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, free)(void *ptr);

Frees memory allocated by this allocator. Freeing a pointer from a different allocator, or after cleanup, is undefined behavior.

#fio_mmap

c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME, mmap)(size_t size);

Allocates directly from the system with mmap semantics. Use this for large, long-lived objects. The allocation is slower but avoids arena bookkeeping.

Parameters:

  • size — bytes to allocate.

Returns: pointer to allocated memory, or NULL on failure.

#fio_malloc_after_fork

c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_after_fork)(void);

Reinitializes allocator locks after fork in the child process. Best effort only: forking a multi-threaded process is unsafe. Prefer calling fio_state_callback_force(FIO_CALL_IN_CHILD); to reset all registered allocators.

#fio_realloc_is_safe

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, realloc_is_safe)(void);

Returns non-zero if the allocator zeroes allocations, which makes fio_realloc2 safe for the uncopied tail.


#Aligned Allocation API

The aligned API requests a minimum pointer alignment. fio_malloc_aligned and fio_calloc_aligned are thin wrappers around fio_realloc_aligned, which is the single entry point.

#fio_realloc_aligned

c
SFUNC void *FIO_MEM_ALIGN FIO_NAME(FIO_MEMORY_NAME,
                                   realloc_aligned)(void *ptr,
                                                    size_t new_size,
                                                    size_t copy_len,
                                                    size_t alignment);

Reallocates ptr to new_size (copying at most copy_len bytes, like fio_realloc2), guaranteeing the returned pointer is aligned to at least alignment.

The alignment argument is normalized:

  • 0 selects the allocator default (FIO_MEMORY_ALIGN_SIZE).
  • Non power-of-2 values are rounded down to the nearest power of 2.
  • The effective alignment is the maximum of the (rounded) request, the current alignment of ptr, and FIO_MEMORY_ALIGN_SIZE — alignment never regresses across a realloc.
  • Values above FIO_MEMORY_SYS_ALLOCATION_SIZE fail: returns NULL and sets errno to EINVAL (debug builds also trip FIO_ASSERT_DEBUG).

In-place growth only occurs when the existing pointer already satisfies the requested alignment; otherwise the data is copied to a newly aligned block.

realloc_aligned(NULL, size, 0, alignment) allocates (like malloc_aligned), and realloc_aligned(ptr, 0, copy_len, alignment) frees ptr.

#fio_malloc_aligned

c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME,
                                       malloc_aligned)(size_t size,
                                                       size_t alignment);

Equivalent to fio_realloc_aligned(NULL, size, 0, alignment). A zero size returns the shared zero-allocation pointer (aligned only to FIO_MEMORY_ALIGN_SIZE), which may be freed or realloc'd normally.

#fio_calloc_aligned

c
SFUNC void *FIO_MEM_ALIGN_NEW FIO_NAME(FIO_MEMORY_NAME,
                                       calloc_aligned)(size_t size_per_unit,
                                                       size_t unit_count,
                                                       size_t alignment);

Same as fio_malloc_aligned(size_per_unit * unit_count, alignment), but zeroes the (rounded) allocation. Returns NULL on size overflow.

#fio_alloc_size

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, alloc_size)(size_t minimum_bytes);

Returns the number of usable bytes the allocator actually reserves for a request of minimum_bytes (the size class): rounded up to FIO_MEMORY_ALIGN_SIZE for arena/big-block allocations, and to whole pages (minus the header offset) for page-backed ones. Alignment padding is reserved in addition to the returned value. With FIO_MEMORY_DISABLE the system allocator exposes no rounding guarantee and minimum_bytes is returned unchanged.

#Alignment abstraction macros

c
FIO_MEM_REALLOC_ALIGNED(ptr, old_size, new_size, copy_len, alignment)
FIO_MEM_FREE_ALIGNED(ptr, size)
FIO_MEM_ALLOC_SIZE(size)

FIO_MEM_REALLOC_ALIGNED mirrors FIO_MEM_REALLOC with an added alignment argument and assigns the result to ptr (test with FIO_ASSERT_ALLOC(ptr) after the call, restoring a saved copy on failure). FIO_MEM_FREE_ALIGNED releases memory obtained through the aligned API, and FIO_MEM_ALLOC_SIZE mirrors fio_alloc_size.

The macros route to the active allocator, exactly like FIO_MEM_REALLOC does: the fio allocator when it was included (H___FIO_MALLOC___H), or portable system-allocator fallbacks otherwise. Template-local variants (FIO_MEM_REALLOC_ALIGNED_, FIO_MEM_FREE_ALIGNED_) exist for FIO_MEMORY_NAME template scope, mirroring FIO_MEM_REALLOC_ / FIO_MEM_FREE_.

Pairing rule: with the fio allocator, free accepts any pointer returned by the aligned API (interior pointers included), so FIO_MEM_FREE_ALIGNED is identical to FIO_MEM_FREE. Under FIO_MEMORY_DISABLE on Windows the aligned API uses _aligned_malloc / _aligned_realloc, whose memory must be released with FIO_MEM_FREE_ALIGNED (_aligned_free) — plain free is heap corruption. On POSIX both map to free. Custom allocators that stash alignment bookkeeping near the returned pointer should hook the macro pair, not the fio functions.

#Alignment guarantees per configuration

Configuration Default alignment Aligned API backend Max alignment
fio allocator (any OS) FIO_MEMORY_ALIGN_SIZE (64) interior-pointer slice / big-block / page-backed FIO_MEMORY_SYS_ALLOCATION_SIZE
FIO_MEMORY_DISABLE, POSIX _Alignof(max_align_t) posix_memalign + free FIO_MEMORY_SYS_ALLOCATION_SIZE
FIO_MEMORY_DISABLE, Windows _Alignof(max_align_t) (16) _aligned_malloc / _aligned_realloc / _aligned_free FIO_MEMORY_SYS_ALLOCATION_SIZE
FIO_MEMORY_DISABLE, other _Alignof(max_align_t) over-allocation with stashed raw pointer FIO_MEMORY_SYS_ALLOCATION_SIZE

When FIO_MALLOC_OVERRIDE_SYSTEM is defined, the libc aligned-allocation family is overridden as well (aligned_alloc, posix_memalign, and on Windows _aligned_malloc / _aligned_realloc / _aligned_free), so libc-aligned pointers never reach the fio allocator (and vice versa).


#Configuration Macros

#FIO_MEMORY_NAME

c
#define FIO_MEMORY_NAME fio

Required prefix for the allocator's public symbols. Must be defined before including the header.

#FIO_MALLOC

c
#define FIO_MALLOC

Shortcut that defines the allocator with prefix fio and tunes it for general use. It also redefines FIO_MEM_REALLOC, FIO_MEM_FREE, FIO_MEM_REALLOC_IS_SAFE, and FIO_MEM_ALIGNMENT_SIZE.

#FIO_MEMORY_DISABLE

c
#define FIO_MEMORY_DISABLE

Bypasses the custom allocator and routes all allocations to the system malloc/free.

#FIO_MEMORY_ALIGN_LOG

c
#define FIO_MEMORY_ALIGN_LOG 6

Log2 of the allocation alignment. Clamped to the range 310 (8 to 1024 bytes). Default is 6 (64 bytes).

#FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG

c
#define FIO_MEMORY_SYS_ALLOCATION_SIZE_LOG 21

Log2 of the chunk size allocated from the system. Clamped to 1724. Default is 21 (~2 MB).

#FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG

c
#define FIO_MEMORY_BLOCKS_PER_ALLOCATION_LOG 2

Log2 of the number of blocks per system chunk. Range 04. Affects fragmentation and the threshold for big allocations.

#FIO_MEMORY_ENABLE_BIG_ALLOC

c
#define FIO_MEMORY_ENABLE_BIG_ALLOC 1

When enabled, large allocations can consume an entire chunk as a single big block. May increase fragmentation for long-lived objects.

#FIO_MEMORY_ARENA_COUNT

c
#define FIO_MEMORY_ARENA_COUNT -1

Number of arenas. Negative or zero means dynamic selection based on CPU core count, capped by FIO_MEMORY_ARENA_COUNT_MAX and fallback FIO_MEMORY_ARENA_COUNT_FALLBACK.

#FIO_MEMORY_ARENA_COUNT_FALLBACK

c
#define FIO_MEMORY_ARENA_COUNT_FALLBACK 24

Default arena count when dynamic CPU detection fails.

#FIO_MEMORY_ARENA_COUNT_MAX

c
#define FIO_MEMORY_ARENA_COUNT_MAX 64

Upper bound for dynamic arena count.

#FIO_MEMORY_USE_THREAD_MUTEX

c
#define FIO_MEMORY_USE_THREAD_MUTEX 0

When 1, arenas protect themselves with pthread mutexes instead of spinlocks. Default is 1 when FIO_MEMORY_ARENA_COUNT is positive.

#FIO_MEMORY_INITIALIZE_ALLOCATIONS

c
#define FIO_MEMORY_INITIALIZE_ALLOCATIONS \
        FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT

When 1, allocations return zeroed memory and realloc2 zeroes uncopied bytes. Default is 1.

#FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT

c
#define FIO_MEMORY_INITIALIZE_ALLOCATIONS_DEFAULT 1

Default value for FIO_MEMORY_INITIALIZE_ALLOCATIONS.

#FIO_MEMORY_CACHE_SLOTS

c
#define FIO_MEMORY_CACHE_SLOTS 4

Number of freed chunks to keep cached before returning memory to the system.

#FIO_MEMORY_WARMUP

c
#define FIO_MEMORY_WARMUP 0

If set to a positive number, that many arenas pre-allocate a block at startup. Usually best left at 0.

#FIO_MEM_SYS_ALLOC, FIO_MEM_SYS_REALLOC, FIO_MEM_SYS_FREE

c
#define FIO_MEM_SYS_ALLOC(pages, alignment_log) /* ... */
#define FIO_MEM_SYS_REALLOC(ptr, old_pages, new_pages, alignment_log) /* ... */
#define FIO_MEM_SYS_FREE(ptr, pages) /* ... */

Override all three to replace the system allocation backend. Alignment is essential and may be large.


#Introspection

These functions are exposed for debugging. Treat them as unstable.

#fio_malloc_arenas

c
SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arenas)(void);

Returns the number of arenas.

#fio_malloc_block_size

c
SFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_block_size)(void);

Returns the block size used for arena allocations.

#fio_malloc_sys_alloc_size

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_sys_alloc_size)(void);

Returns the system chunk size in bytes.

#fio_malloc_cache_slots

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_cache_slots)(void);

Returns the configured number of cache slots.

#fio_malloc_alignment

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment)(void);

Returns the allocation alignment in bytes.

#fio_malloc_alignment_log

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alignment_log)(void);

Returns log2 of the allocation alignment.

#fio_malloc_alloc_limit

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_alloc_limit)(void);

Returns the size above which allocations bypass arena blocks.

#fio_malloc_arena_alloc_limit

c
FIO_IFUNC size_t FIO_NAME(FIO_MEMORY_NAME, malloc_arena_alloc_limit)(void);

Returns the arena block size limit.

#fio_malloc_print_state

c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_state)(void);

Prints allocator state to stderr.

#fio_malloc_print_free_block_list

c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_free_block_list)(void);

Prints the free-block list to stderr.

#fio_malloc_print_settings

c
SFUNC void FIO_NAME(FIO_MEMORY_NAME, malloc_print_settings)(void);

Prints allocator settings to stderr.


#Example

c
#define FIO_MALLOC
#include "fio-stl.h"

int main(void) {
  char *buf = fio_malloc(256);
  if (!buf) return -1;

  fio_memcpy(buf, "hello", 5);
  fprintf(stderr, "%s\n", buf);

  char *buf2 = fio_realloc2(buf, 512, 5);
  if (!buf2) {
    fio_free(buf);
    return -1;
  }
  buf = buf2;

  fio_free(buf);
  return 0;
}