Spaces takes a different approach. It uses 64KB-aligned slabs, and the metadata lookup is just a pointer mask (ptr & ~0xFFFF).
The trade-off is that every free() incurs an L1 cache miss to read the slab header, and there is a 64KB virtual memory floor per slab. But in exchange, you get zero-external-metadata regions, instant teardown of massive structures like ASTs, and performance that surprisingly keeps up with jemalloc on cross-thread workloads (I included the mimalloc-bench scripts in the repo).
It's Linux x86-64 only right now. I'm curious if systems folks think this chunk API is a pragmatic middle ground for memory management, or if the cache-miss penalty on free() makes the pointer-masking approach a dead end for general use.
```c ((PageSize) (chunk->pageSize - ((PageSize) ((PageSize) ((PageSize) (sizeof(Page) + (sizeof(struct _Block))) + (PageSize) ((sizeof(double)) - 1u)) & ((PageSize) (~((PageSize) ((sizeof(double)) - 1u)))))) - ((PageSize) ((PageSize) ((PageSize) ((sizeof(FreeBlock) + sizeof(PageSize))) + (PageSize) (((((sizeof(double)) > (4)) ? (sizeof(double)) : (4))) - ```
[0]: https://github.com/xtellect/spaces/blob/422dbba85b5a7e9a209a...
[1]: https://github.com/xtellect/spaces/blob/422dbba85b5a7e9a209a...
[2]: https://github.com/xtellect/spaces/blob/422dbba85b5a7e9a209a...
My hunch tells me it may be the result of macro-expansion in C (cc -E ...), etc. So it's likely there's a larger code base with multiple files and they expanded it into a one large C file (sometimes called an amalgamation build) and called it a day.
By they, I mean the OP, a script or an AI (or all three).
One big commit definitely doesn’t help with creating confidence in this project.
There was once a time when sharing code had a social obligation.
This attitude you have isn't in the same spirit. GitHub (or any forge) was never meant to be a garbage dumping ground for whatever idea you cooked up at 3AM.
Have you looked at the code? It was clearly generated in one form or another (see the other comments).
The author created a new GitHub account and this is their first repository. It looks to be generated from another code base as a sorta amalgamation (either through code generation, ai, or another means).
We're supposed to implicitly trust this person (new GitHub account, first repository, no commit history, 10k+ lines of complicated code).
Jia Tan worked way too hard, all they had to do was upload a few files and share on HN :)
That would be rather foolish even with a fully viewable history.
I don't understand why you're so worked up about this—nobody is forcing you to use the code.
When you share code it's presumably for people to use. It is often useful to have commit history to establish a few things (trust in the author, see their thought process, debug issues, figure out how to use things, etc).
> You completely failed to establish why making a single commit is indicative of it being garbage.
A single commit doesn't mean it's garbage. It erodes trust in the author and the project. It makes it hard for me to use the code, which is presumably why you share code.
My garbage code response was in regards to the growing trend to code (usually with ai) some idea, slap an initial commit on it and throw it on GitHub (like using a napkin and tossing it in the rubbish bin).
Edit: Homie. Why is bench.sh fetching external resources? Call me old fashioned, but it would be nice if when I cloned the repository (and checked out any submodules that may exist), I've got everything I need, right there.
I don't see how that could possibly be true. Sounds like a low-ball estimate.
Also i wish to point out that the "tcmalloc" being used as a baseline in these performance claims is Ye Olde tcmalloc, the abandoned and now community-maintained version of the project. The current version of tcmalloc is a completely different thing that the mimalloc-bench project doesn't support (correctly; I just checked).
Spaces is a single-file C allocator for Linux x86-64. It works as a
drop-in malloc replacement, but its distinctive feature is that it
also gives you explicit heap regions: create a heap for a subsystem,
cap its memory, inspect every live allocation, share it across
processes, and destroy the entire region in one call.
Use cases:
spaces_chunk_destroy() call instead of chasing pointers.gcc -O3 -pthread -fPIC -c spaces.c
ar rc libspaces.a spaces.o
gcc your_app.c -Wl,--whole-archive libspaces.a -Wl,--no-whole-archive -lpthread
#include "spaces.h"
// A bounded cache with enforced ceiling
SpacesChunk cache = spaces_chunk_create(0);
spaces_chunk_set_ceiling(cache, 256 * 1024 * 1024);
void *p = spaces_chunk_alloc(cache, request_size, 0);
if (!p) evict_oldest(); // ceiling enforced by the allocator
spaces_chunk_destroy(cache); // instant teardown, everything freed
// Or just use it as malloc — same binary, no code changes
void *p = malloc(4096);
free(p);
I maintain a codebase where different subsystems need their own memory budgets, and where cleanup is the hardest part: a parser allocates a tree, the tree gets transformed, intermediate structures are discarded, and the final output lives in a separate region. With a standard allocator you either track and free every node, or you leak.
I wanted an allocator where I could say: "this heap belongs to the parser; when parsing is done, destroy it." And I wanted the allocator itself to be fast enough that I wouldn't pay a tax for the organisational benefit.
Existing options fell into two camps. The fast allocators
(jemalloc, tcmalloc, mimalloc) are black boxes — fast malloc/free,
nothing else. The region allocators (Apache APR pools, Loki's small
object allocator) give you structure but aren't competitive as general
malloc replacements. Spaces tries to be both: a fast malloc with
the internal structure to support regions, introspection, budgets,
shared memory, and per-subsystem tuning.
The trade-off I chose: every free() reads one cache line from the slab
header at the 64 KB boundary of the freed pointer. That costs ~5 ns
when the line is cold. In exchange, free() discovers the size class,
the owning thread, and the region identity in a single load, with no
page map, no radix tree, and no external metadata structure.
Unlike typical drop-in allocators, Spaces also exposes region-style heaps, live-allocation walking, per-region memory ceilings, and cross-process shared heaps — 62 functions in the same binary.
SpacesChunk parse_heap = spaces_chunk_create(0);
AstNode *root = parse(parse_heap, source); // all nodes from this heap
// ... transform, analyse, error-check ...
spaces_chunk_destroy(parse_heap); // one call frees the entire tree
This is the difference between leak-prone cleanup code and one O(1) destroy.
printf("pool: %zu allocs, %zu bytes\n",
spaces_chunk_count(conn_pool),
spaces_chunk_size(conn_pool));
SpacesChunkEntry e;
while (spaces_chunk_walk(conn_pool, &e) == SPACES_CHUNK_OK)
if (e.isInUse)
printf(" live: %p %zu bytes\n", e.entry, e.size);
No recompilation, no Valgrind, no profiler.
spaces_chunk_set_ceiling(cache, 256 * 1024 * 1024);
// Every allocation that would push the chunk past 256 MB returns NULL
// or invokes your error handler.
For uniform-sized objects (linked list nodes, connection structs): zero per-allocation overhead, zero fragmentation, O(1) alloc and free.
SpacesChunk pool = spaces_chunk_create_fixed(sizeof(Node), 0, 0);
Node *n = spaces_chunk_alloc_fixed(pool);
// On UNIX the name parameter is a System V key, not a string.
key_t key = ftok("/tmp/myapp", 'A');
SpacesChunk shm = spaces_chunk_create_shared((const char *)(intptr_t)key, 64*1024*1024, 0);
// Another process attaches with the same key:
SpacesChunk shm = spaces_chunk_attach_shared(NULL, (const char *)(intptr_t)key);
Every chunk has its own page size, growth increment, small-block threshold, and sub-chunk count. Tune the hot chunk for throughput, the cold chunk for density.
Tell the allocator a thread owns all its allocations. It drops locking and auto-frees everything at thread exit.
spaces_default_chunk_thread_exclusive(
SPACES_THREAD_EXCLUSIVE_ON | SPACES_FREE_ON_THREAD_TERM);
// Replace the default interactive handler for non-interactive programs.
// Double frees, bad pointers, ceiling violations — caught and reported
// through your callback instead of silent corruption.
spaces_set_error_handler(my_handler);
Competitive with common allocators on standard benchmarks. The table below summarises both wins and losses.
| Workload | What it tests | Spaces | Standing |
|---|---|---|---|
| larson | cross-thread producer-consumer | 124 M ops/s | #2 — beats jemalloc, snmalloc, tcmalloc, Hoard |
| malloc-large | 5–25 MB blocks | 6.0 ms/alloc | #2 — beats jemalloc, snmalloc, Hoard |
| mleak | thread create/destroy | 0.26 s | #2 — beats jemalloc, snmalloc |
| glibc-simple | single-thread alloc/free | 6.9 s | #3 — beats jemalloc, snmalloc |
| xmalloc-test | producer-consumer | 10.6 M/s | #3 — beats snmalloc, tcmalloc |
| mstress | 16-thread mixed | 6.9 s | #3 — beats tcmalloc, Hoard |
| spacesbench | 16-thread histogram | 4.0 s | #3 — beats snmalloc, tcmalloc, Hoard |
| glibc-thread | 8-thread random | 493 M iter | #6 |
Top 3 on 7 of 8 workloads. The one loss (glibc-thread)
comes from the slab-header cache miss on free() when allocation sizes
vary widely across many threads.
Reproduce: ./bench.sh (auto-downloads workloads from mimalloc-bench, builds them against libspaces.a, and reports median time and peak RSS over 3 runs). Use ./bench.sh -s to skip the download on subsequent runs, or ./bench.sh -n N to change the number of repetitions.
Heavy thread-churn workloads — the per-thread slab working set across 52 size classes doesn't stay cache-hot when 500 threads cycle rapidly.
x86-64 Linux only in this build. The source support for other platforms will be added soon but this single-file distribution is pre-processed for one target.
Minimum allocation is 8 bytes. No 4-byte or 2-byte classes.
64 KB virtual footprint per slab even if few objects are live.
Every free() reads the slab header — one potential L1 miss
per free. This is the core trade-off for the zero-external-metadata
design.
free(ptr) finds metadata by masking:
ptr & ~0xFFFF. No page map, no radix tree.malloc/free fast path: zero atomics, zero locks.mmap with huge-page support.| Design choice | Benefit | Cost |
|---|---|---|
| 64 KB aligned slabs | O(1) metadata lookup | 64 KB min virtual footprint |
| 4 KB slab header | memalign ≤ 4096 on fast path | 6% waste per segment |
| Intrusive freelist | 832 B total cache, L1-friendly | overwrites first 8 bytes of freed object |
| Per-slab remote-free stack | no global lock on cross-thread free | one CAS per remote free |
| 4 MB batch regions | ~1 mmap per 64 slabs | coarser virtual granularity |
| No MADV_FREE on return | faster slab reuse | RSS stays elevated until reuse |
gcc -O3 -pthread -fPIC -c spaces.c
ar rc libspaces.a spaces.o && ranlib libspaces.a
Drop-in malloc (replaces libc):
gcc app.c -Wl,--whole-archive libspaces.a -Wl,--no-whole-archive -lpthread
Chunk API only (libc malloc untouched):
gcc app.c libspaces.a -lpthread
Self-test:
make test
Benchmarks:
./bench.sh # download (once), build, run all
./bench.sh -s # skip download, rebuild + run
./bench.sh -n 5 # 5 repetitions per test
Include spaces.h in every translation unit that calls the Spaces API. If the file also includes headers that declare malloc, such as <stdlib.h>, include those headers first and spaces.h afterward.
Example:
#include <stdlib.h>
#include "spaces.h"
If the compiler does not already search the directory that contains spaces.h, add the current project directory to the include path. In this distribution the public header sits next to spaces.c, so a local build typically uses:
-I.
The core allocator (malloc, calloc, realloc, free) is fully
thread-safe and self-initializing. Multiple threads may call these
functions concurrently without any explicit setup — the library performs
lock-free, atomic initialization on the first allocation.
The chunk subsystem (spaces_chunk_create*, spaces_chunk_alloc, etc.)
also initializes on demand. Calling spaces_init() explicitly is
optional; do so only when you need to guarantee that process-wide state
is configured before any allocation occurs (for example, to set
spaces_default_page_size before the default chunk is created).
The process-wide default chunk used by the C allocation wrappers is
serialized unless you deliberately disable that behavior through
spaces_default_flags. Chunks created explicitly with
spaces_chunk_create*() are not serialized unless you pass
SPACES_CHUNK_DEFAULT or SPACES_CHUNK_SERIALIZE. If a chunk is
touched by only one thread at a time, leave serialization off to avoid
unnecessary contention.
When Spaces is linked into a shared object that is loaded later with dlopen(), the process may already have resolved malloc to the C runtime before your module is loaded. In that situation your module can still end up calling the CRT allocator unless the shared object is linked with symbolic binding. Use -Bsymbolic when linking the shared library if you need the library's internal calls to resolve to Spaces at runtime.
Shared chunks are created with spaces_chunk_create_shared() and attached
with spaces_chunk_attach_shared(). On UNIX these functions use System V
shared memory (shmget/shmat). The name parameter is not a
string — it is a key_t value that must be cast to const char * for the
call. The typical pattern is:
#include <sys/ipc.h>
key_t key = ftok("/tmp/myapp", 'A');
SpacesChunk shm = spaces_chunk_create_shared((const char *)(intptr_t)key, size, 0);
The (intptr_t) intermediate cast avoids sign-extension and pointer-width
warnings. In the attaching process, use the same key:
SpacesChunk shm = spaces_chunk_attach_shared(NULL, (const char *)(intptr_t)key);
Shared chunks behave as though SPACES_CHUNK_SHARED and SPACES_CHUNK_SERIALIZE were already included. The caller therefore does not need to pass those flags again. The size argument is the total backing size of the shared-memory object. On UNIX it is fixed at creation time and cannot later be enlarged.
Capacity planning matters for shared chunks. Roughly 4 KB is consumed by chunk metadata, and in normal use a meaningful portion of the segment remains free space. A practical planning rule is to size a shared chunk to at least 2 * peak_payload + 4 KB if you expect to allocate peak_payload bytes at maximum load.
A shared chunk can be used concurrently by multiple processes. Error reports are emitted in the process that detects the fault, not necessarily the process that created the shared chunk. spaces_chunk_create_shared() must finish in the creating process before spaces_chunk_attach_shared() can succeed elsewhere because the synchronization primitive is stored inside the shared mapping and is not valid until initialization is complete.
To detach from a shared chunk in one process, call spaces_chunk_destroy() in that process. The shared object is removed only after the final attached process destroys it. Ownership is collective rather than tied to the creator.
spaces_chunk_create_area() and spaces_chunk_create_area_ex() let the allocator manage a memory region that you supply. The caller remains responsible for the lifetime of that region. Destroying the chunk releases allocator-side bookkeeping and synchronization objects, but it does not free the user-owned area itself.
The built-in Linux error handler writes a diagnostic to stderr and, for
recoverable failures, reads a single-character response from stdin
(abort/ignore/retry). This interactive prompt is unsuitable for servers,
daemons, CI, and any context where stdin is not a terminal.
Replace it early in main() for non-interactive programs:
static int my_handler(SpacesErrorInfo *err)
{
fprintf(stderr, "spaces: error %d\n", err->errorCode);
return 0; // 0 = ignore (return error to caller)
}
int main(void)
{
spaces_set_error_handler(my_handler);
// ...
}
Returning 0 from the handler tells the allocator to propagate the failure
to the caller as a NULL return or error code. Calling abort() inside
the handler terminates the process immediately.
The built-in handler's three responses:
abort() and terminates the process.The following values summarize the documented Linux defaults for this allocator:
| Description | 64-bit Value | Related API |
|---|---|---|
| Default page size | 64 KB | spaces_chunk_set_page_size |
| Default small-block threshold | 256 B | spaces_chunk_set_small_block_size |
| Per-allocation overhead, fixed blocks | 0 B | spaces_chunk_alloc_fixed |
| Per-allocation overhead, blocks < 256 B | 0 B | spaces_chunk_alloc, malloc |
| Per-allocation overhead, var ptr blocks | 2 B | spaces_chunk_alloc, malloc |
| Allocation granularity | 8 B | all APIs |
| Minimum fixed block size | 4 B | spaces_chunk_alloc_fixed |
| Minimum variable block size | 14 B | spaces_chunk_alloc |
| Per-page allocator overhead | 44 B | internal |
| Minimum page size | 4 KB | spaces_chunk_set_page_size |
| Maximum page size | 64 KB | spaces_chunk_set_page_size |
| Empty chunk footprint | 16 KB | spaces_chunk_create* |
| Maximum sub-chunk count | 256 | spaces_chunk_set_max_subchunks |
| Process free-byte threshold | ULONG_MAX |
spaces_process_set_free_bytes |
| Process heap limit | ULONG_MAX |
spaces_process_set_heap_limit |
| Process grow increment | 1 MB | spaces_process_set_grow_increment |
| Chunk free-byte threshold | 1 MB | spaces_chunk_set_free_bytes |
| Large-block threshold | 32 MB | spaces_process_set_large_threshold |
This section documents the public API exported by spaces.h.
Prototype
void *malloc(size_t size);
Returns
A pointer to at least size bytes, or NULL if the request cannot be satisfied.
Description Allocates from the process default chunk. The returned memory is not initialized. A request of zero bytes still goes through the allocator and is treated as a zero-length allocation case by this API.
Notes
spaces_init_default_chunk().spaces_default_chunk with chunk APIs such as spaces_chunk_size()
or spaces_chunk_shrink() when you need information about the heap
backing malloc().spaces_chunk_ptr_size() or spaces_usable_size().Prototype
void *calloc(size_t nobj, size_t size);
Returns
A zero-filled allocation covering nobj * size bytes, or NULL on failure.
Description
Identical to malloc() with respect to chunk ownership, but clears the full
returned region before control returns to the caller.
Notes
Prototype
void *realloc(void *ptr, size_t size);
Returns
A pointer to the resized block, or NULL if the resize fails.
Description Resizes a block obtained from the default allocator entry points or chunk APIs. The block may move. If it moves, the original storage is released only after the contents have been copied.
Notes
NULL behaves like malloc(size).NULL pointer with size == 0 yields NULL and
deallocates the original block.spaces_chunk_realloc() when you need resize policy flags.Prototype
void free(void *ptr);
Returns No value.
Description Releases a block obtained from the standard allocator wrappers or the compatible Spaces allocation APIs.
Notes
free(NULL) is a no-op.Prototype
void spaces_core_init(void);
Returns No value.
Description Initializes the modern slab allocator core used by the malloc-family wrappers.
Notes
Prototype
void *spaces_malloc(size_t size);
Returns
A newly allocated block or NULL.
Description
Direct entry point to the Spaces-backed malloc implementation.
Notes
Prototype
void *spaces_calloc(size_t nobj, size_t size);
Returns
A zero-filled block or NULL.
Description
Direct entry point to the Spaces-backed calloc implementation.
Prototype
void *spaces_realloc(void *ptr, size_t size);
Returns
A resized block or NULL.
Description
Direct entry point to the Spaces-backed realloc implementation.
Prototype
void *spaces_memalign(size_t alignment, size_t size);
Returns
An aligned block or NULL.
Description Allocates storage whose address satisfies the requested power-of-two alignment.
Notes
Prototype
size_t spaces_usable_size(void *ptr);
Returns The usable size of the allocation, or zero for an invalid pointer in the current implementation.
Description Reports the allocator's actual payload size for a live allocation.
Notes
Prototype
void spaces_thread_cleanup(void);
Returns No value.
Description Releases per-thread allocator state associated with the calling thread.
Notes
Prototype
int spaces_init(void);
Returns Non-zero on success, zero on failure.
Description Registers the process with the Spaces subsystem and initializes state that chunk APIs depend on.
Notes
spaces_init() is optional. Use it when you need process-wide
configuration (page size, flags) to take effect before the first
allocation triggers lazy initialization.spaces_shutdown() must be called the
same number of times to fully tear the subsystem down.Prototype
int spaces_shutdown(void);
Returns Non-zero if the process was registered and the count was decremented; zero if nothing was registered.
Description Unregisters the current process from the Spaces subsystem and frees allocator-owned global state when the registration count reaches zero.
Notes
Prototype
SpacesVersion spaces_version(void);
Returns A packed integer version identifier.
Description Returns the allocator version in encoded major/minor/update form.
Notes
SPACES_MAJOR_VERSION(ver) for unpacking, but those macros are not part
of the current public header in this build.Prototype
SpacesChunk spaces_init_default_chunk(void);
Returns
The process default chunk, or NULL if creation fails.
Description Creates the default chunk used by the standard allocation wrappers if it does not already exist, otherwise returns the existing chunk.
Notes
malloc().spaces_default_block_size, spaces_default_page_size, and
spaces_default_flags.Prototype
int spaces_free_default_chunk(void);
Returns Implementation-defined success indicator.
Description Destroys the process default chunk through the supported control path.
Notes
spaces_default_chunk to spaces_chunk_destroy(). The
default chunk has its own teardown API.Prototype
int spaces_default_chunk_thread_exclusive(unsigned flags);
Returns Non-zero on success, zero on failure.
Description Switches the calling thread between the shared default chunk and a thread- private default chunk policy.
Notes
SPACES_THREAD_EXCLUSIVE_ON gives the thread its own default chunk and
removes cross-thread synchronization for allocations done there.SPACES_FREE_ON_THREAD_TERM additionally arranges for allocations from
that thread-private chunk to be reclaimed when the thread exits.Prototype
SpacesChunk spaces_chunk_create(unsigned flags);
Returns
A new chunk handle or NULL.
Description Creates a general-purpose chunk for subsequent pointer-based or fixed-size allocations.
Notes
SPACES_CHUNK_SERIALIZE,
SPACES_CHUNK_VIRTUAL_LOCK, SPACES_CHUNK_ZERO_INIT, and
SPACES_CHUNK_DEFAULT.Prototype
SpacesChunk spaces_chunk_create_fixed(unsigned short block_size, size_t count, unsigned flags);
Returns
A chunk handle or NULL.
Description Creates a chunk configured for fixed-size allocation and optionally preallocates space for an initial number of blocks.
Notes
spaces_chunk_info() or spaces_chunk_ptr_size().Prototype
SpacesChunk spaces_chunk_create_area(void *addr, size_t size, unsigned flags);
Returns
A chunk handle or NULL.
Description Builds a chunk inside caller-supplied storage.
Notes
addr and extending
for size bytes.Prototype
SpacesChunk spaces_chunk_create_area_ex(void *addr, size_t size, unsigned flags, void *security);
Returns
A chunk handle or NULL.
Description
Extended form of spaces_chunk_create_area() with an extra platform-
specific security attribute pointer.
Notes
security argument exists for compatibility with platforms that
associate access metadata with the mapping or object that backs the chunk.Prototype
SpacesChunk spaces_chunk_create_shared(const char *name, size_t size, unsigned flags);
Returns
A shared chunk handle or NULL.
Description Creates a chunk whose storage can be attached from multiple processes.
Notes
name parameter is a System V key_t value cast to
const char *, not a string. Use ftok() to obtain the key and
(const char *)(intptr_t)key for the cast.spaces_chunk_create_shared() and
spaces_chunk_attach_shared() in the public interface.Prototype
SpacesChunk spaces_chunk_attach_shared(SpacesChunk chunk, const char *name);
Returns
A chunk handle in the current process or NULL.
Description Attaches an already-created shared chunk to the current process.
Notes
name is a key_t cast — see spaces_chunk_create_shared()
above for the correct calling pattern.Prototype
int spaces_chunk_destroy(SpacesChunk chunk);
Returns Non-zero on success, zero on failure.
Description Releases all allocations owned by a chunk in one operation and destroys the chunk itself.
Notes
Prototype
void *spaces_chunk_alloc(SpacesChunk chunk, size_t size, unsigned flags);
Returns
A pointer on success, otherwise NULL.
Description Allocates a variable-size block from a specific chunk.
Notes
SPACES_FLAG_ZERO_INIT, SPACES_FLAG_NO_GROW,
SPACES_FLAG_NO_EXTERNAL, and SPACES_FLAG_RESIZEABLE.malloc().Prototype
void *spaces_chunk_alloc_fixed(SpacesChunk chunk);
Returns
A pointer or NULL.
Description Allocates one fixed-size element from a fixed-size chunk.
Notes
Prototype
void *spaces_chunk_alloc_aligned(SpacesChunk chunk, size_t size, size_t alignment, unsigned flags);
Returns
An aligned pointer or NULL.
Description Allocates from a specific chunk while enforcing a caller-specified alignment.
Notes
Prototype
void *spaces_chunk_alloc_aligned_offset(SpacesChunk chunk, size_t size, size_t alignment, size_t offset, unsigned flags);
Returns
An aligned pointer or NULL.
Description
Like spaces_chunk_alloc_aligned(), but enforces alignment relative to an
offset into the returned block.
Notes
Prototype
int spaces_chunk_free(void *ptr);
Returns Non-zero on success, zero on failure.
Description Frees a variable-size block previously allocated by Spaces.
Notes
spaces_chunk_destroy() rather than
iterating over allocations one by one.Prototype
int spaces_chunk_free_fixed(void *ptr);
Returns Non-zero on success, zero on failure.
Description Frees a block that was allocated with the fixed-size allocator.
Notes
SPACES_ERR_NOT_FIXED_SIZE in the public error code set.Prototype
int spaces_chunk_free_aligned(void *ptr);
Returns Non-zero on success, zero on failure.
Description Frees a block allocated by one of the aligned allocation entry points.
Prototype
void *spaces_chunk_realloc(void *ptr, size_t size, unsigned flags);
Returns
A pointer to the resized block, or NULL.
Description Resizes an existing Spaces allocation with control over zero-fill, in-place behavior, and chunk growth policy.
Notes
SPACES_FLAG_RESIZE_IN_PLACE makes the call fail instead of moving the
block.SPACES_FLAG_ZERO_INIT clears the newly added portion when the block
grows.SPACES_FLAG_RESIZEABLE trades extra space for a better chance of a later
growth succeeding in place.Prototype
void *spaces_chunk_realloc_aligned(void *ptr, size_t size, size_t alignment, unsigned flags);
Returns
A resized aligned block or NULL.
Description
Aligned variant of spaces_chunk_realloc().
Prototype
void *spaces_chunk_realloc_aligned_offset(void *ptr, size_t size, size_t alignment, size_t offset, unsigned flags);
Returns
A resized aligned block or NULL.
Description
Offset-aware aligned variant of spaces_chunk_realloc().
Prototype
size_t spaces_chunk_ptr_size(void *ptr);
Returns
The allocator's exact size for the block, or SPACES_ERROR_RET on error.
Description Reports the real size of a block returned by Spaces.
Notes
Prototype
size_t spaces_chunk_size_aligned(void *ptr);
Returns The size associated with an aligned allocation.
Description Returns the managed size for an aligned allocation block.
Prototype
SpacesPointerStatus spaces_chunk_ptr_check(SpacesChunk chunk, void *ptr);
Returns
SPACES_PTR_OK, SPACES_PTR_FREE, or SPACES_PTR_WILD.
Description Validates that a pointer refers to a recognized Spaces allocation and optionally that it belongs to a specific chunk.
Notes
chunk is NULL, the function searches accessible chunks for
ownership.Prototype
SpacesChunkStatus spaces_chunk_check_aligned(SpacesChunk chunk, void *ptr);
Returns A chunk status code.
Description Performs chunk validation in the aligned-allocation path.
Notes
Prototype
unsigned spaces_chunk_set_page_size(SpacesChunk chunk, unsigned size);
Returns The previous page size on success, otherwise zero or an error-path value.
Description Changes the size of pages from which the chunk sub-allocates.
Notes
Prototype
int spaces_chunk_set_block_size(SpacesChunk chunk, unsigned short size);
Returns Non-zero on success, zero on failure.
Description
Defines the fixed block size used by spaces_chunk_alloc_fixed().
Notes
malloc() or spaces_chunk_alloc().Prototype
int spaces_chunk_set_small_allocator(SpacesChunk chunk, SpacesSmallAllocator alg);
Returns Non-zero on success, zero on failure.
Description Selects the algorithm used for small allocations in a chunk.
Notes
SPACES_SMALL_NONE disables the specialized small-block path.SPACES_SMALL_V3 is the older heterogeneous-page algorithm with one byte
of per-block overhead and no cross-size recycling.SPACES_SMALL_V5 is the newer zero-overhead homogeneous-page design that
can recycle page space across block sizes and chunk uses.Prototype
int spaces_chunk_set_small_block_size(SpacesChunk chunk, unsigned short size);
Returns Non-zero on success, zero on failure.
Description Sets the threshold below which variable-size allocations are routed to the small-block allocator.
Notes
Prototype
size_t spaces_chunk_set_floor(SpacesChunk chunk, size_t floor);
Returns
The previous floor value, or SPACES_ERROR_RET on failure.
Description Sets the minimum chunk footprint that shrink operations should preserve.
Notes
Prototype
size_t spaces_chunk_set_ceiling(SpacesChunk chunk, size_t ceiling);
Returns
The previous ceiling value, or SPACES_ERROR_RET on failure.
Description Caps the total amount of operating-system memory a chunk may consume.
Notes
SPACES_ERR_EXCEEDED_CEILING and may trigger internal compaction and
shrink attempts before ultimately failing.Prototype
size_t spaces_chunk_set_free_bytes(SpacesChunk chunk, size_t bytes);
Returns The previous retained-free-byte threshold.
Description Controls how much empty page capacity a chunk keeps available for reuse.
Notes
Prototype
size_t spaces_chunk_set_grow_increment(SpacesChunk chunk, size_t inc);
Returns The previous increment.
Description Sets the chunk-level growth quantum used when the chunk needs more backing memory.
Notes
Prototype
unsigned spaces_chunk_set_max_suballoc(SpacesChunk chunk, unsigned size);
Returns The previous sub-allocation threshold.
Description Sets the largest size that may still be carved out of an existing page instead of receiving a dedicated page.
Notes
Prototype
int spaces_chunk_set_max_subchunks(SpacesChunk chunk, unsigned count);
Returns Non-zero on success, zero on failure.
Description Caps how many sub-chunks a serialized chunk may create to reduce contention in SMP workloads.
Notes
Prototype
int spaces_chunk_set_serialization(SpacesChunk chunk, int enable);
Returns The previous serialization setting.
Description Turns chunk serialization on or off at runtime.
Notes
Prototype
int spaces_chunk_set_page_resizing(SpacesChunk chunk, int enable);
Returns Non-zero on success, zero on failure.
Description Enables or disables variable page resizing for the chunk.
Notes
Prototype
size_t spaces_chunk_preallocate(SpacesChunk chunk, size_t bytes, SpacesBlockType type);
Returns
The number of bytes actually added, or SPACES_ERROR_RET on error.
Description Pulls backing memory into a chunk before the application needs it and formats that memory as free blocks of the requested type.
Notes
spaces_chunk_set_floor() when you want to reserve
memory for a chunk ahead of time and keep it from being returned
immediately.Prototype
size_t spaces_chunk_shrink(SpacesChunk chunk);
Returns The number of bytes returned to the operating system, or zero if no shrink was possible.
Description Compacts moveable pages and frees page backing that is no longer needed.
Notes
Prototype
size_t spaces_chunk_size(SpacesChunk chunk);
Returns The total OS memory currently consumed by the chunk.
Description Reports footprint, not payload usage.
Notes
spaces_chunk_walk() or your own accounting.Prototype
size_t spaces_chunk_count(SpacesChunk chunk);
Returns The net number of allocations from the chunk.
Description Reports allocations minus frees across the pages owned by the chunk.
Notes
Prototype
int spaces_chunk_info(SpacesChunk chunk, void *ptr, SpacesChunkInfo *info);
Returns Non-zero on success, zero on failure.
Description
Fills a SpacesChunkInfo structure using either a chunk handle, a pointer
owned by a chunk, or both.
Notes
chunk is NULL and ptr is not, the function resolves chunk
ownership from the pointer.type field is a bitwise combination describing what block
kinds are currently allocated in the chunk.Prototype
SpacesChunkStatus spaces_chunk_first(SpacesChunkInfo *info, int all_tasks);
Returns
SPACES_CHUNK_OK, SPACES_CHUNK_END, or a corruption status.
Description
Begins enumeration of existing chunks and writes metadata into info.
Notes
spaces_chunk_next() to continue the walk.all_tasks is a compatibility parameter.Prototype
SpacesChunkStatus spaces_chunk_next(SpacesChunkInfo *info, int all_tasks);
Returns
SPACES_CHUNK_OK, SPACES_CHUNK_END, or a corruption status.
Description
Continues a chunk enumeration started by spaces_chunk_first().
Notes
Prototype
SpacesChunkStatus spaces_chunk_walk(SpacesChunk chunk, SpacesChunkEntry *entry);
Returns
SPACES_CHUNK_OK while entries remain, SPACES_CHUNK_END at completion, or a corruption status if validation fails.
Description Walks the blocks of a chunk one entry at a time and returns metadata for each block, free or in-use.
Notes
entry->entry to NULL before the first call.Prototype
int spaces_chunk_check(SpacesChunk chunk);
Returns Non-zero if validation succeeds, zero if corruption is detected.
Description Validates every block in a chunk and reports corruption through the error handler.
Notes
Prototype
int spaces_chunk_lock(SpacesChunk chunk);
Returns Non-zero on success, zero on failure.
Description Acquires thread-exclusive access to a serialized chunk.
Notes
spaces_chunk_unlock().spaces_chunk_walk().Prototype
int spaces_chunk_unlock(SpacesChunk chunk);
Returns Non-zero on success, zero on failure.
Description
Releases one level of chunk-exclusive access previously acquired with
spaces_chunk_lock().
Notes
Prototype
SpacesErrorFn spaces_set_error_handler(SpacesErrorFn fn);
Returns The previously installed handler.
Description Installs a process-local callback for allocator error reporting.
Notes
NULL disables handler-based reporting and leaves APIs to fail by
return value alone.longjmp, exception throw, or another non-local
exit, it must call spaces_error_unwind() immediately before doing so.Prototype
int spaces_default_error_handler(SpacesErrorInfo *err);
Returns Non-zero when the operation should be retried, zero otherwise.
Description Built-in fallback error handler used when no custom handler is installed.
Notes
stderr with abort/ignore/retry behavior
where retry is meaningful.Prototype
void spaces_error_unwind(void);
Returns No value.
Description Tells the allocator that the current error handler will not return normally.
Notes
spaces_set_error_handler().Prototype
int spaces_process_info(SpacesProcessInfo *info);
Returns Non-zero on success, zero on failure.
Description Reports process-wide allocator settings and totals.
Notes
Prototype
size_t spaces_process_set_grow_increment(size_t size);
Returns The previous process-wide increment.
Description Sets the minimum amount of memory the allocator requests from the operating system when feeding chunks from the shared process pool.
Notes
Prototype
size_t spaces_process_set_free_bytes(size_t size);
Returns The previous process-wide retained-free-byte threshold.
Description Controls how much free space the large-block heap keeps before returning memory to the operating system.
Notes
Prototype
size_t spaces_process_set_large_threshold(size_t size);
Returns The previous threshold.
Description Defines the size above which allocations are obtained directly from the operating system instead of the process large-block heap.
Notes
Prototype
size_t spaces_process_set_heap_limit(size_t size);
Returns The previous heap limit.
Description Sets an upper bound on the aggregate amount of operating-system memory the allocator may obtain for its own process-wide heap structures.
Notes
SPACES_ERR_EXCEEDED_HEAP_LIMIT and can also
surface as an out-of-memory condition higher in the stack.Prototype
int spaces_process_use_munmap(int enable);
Returns The previous setting.
Description
Controls whether UNIX builds should release allocator address space with
munmap when possible.
Notes
madvise to discard pages while optionally
using munmap for address-space release.Prototype
int spaces_process_coalesce_allocs(int enable);
Returns The previous setting.
Description Controls whether large OS allocations may be coalesced into results that span underlying VM allocation boundaries.
Notes
Prototype
void spaces_process_flush_all(void);
Returns No value.
Description Forces release of process-level allocator backing state after all chunks have already been destroyed.
Notes
Prototype
void spaces_process_disable_fork_handlers(void);
Returns No value.
Description
Disables allocator-installed fork() handlers.
Notes
Prototype
size_t spaces_process_set_page_cache_size(int page_multiple, size_t size);
Returns The previous page-cache setting.
Description Adjusts the process-level page cache used for recycled pages.
Notes
SpacesChunk: opaque handle for a managed chunk.SpacesVersion: packed unsigned version value.SpacesThreadId: unsigned long thread identifier.SpacesErrorCode: allocator error code enumeration.SpacesBlockType: describes fixed, variable, external, or free block classes.SpacesSmallAllocator: selects the small-block allocation algorithm.SpacesChunkStatus: result of chunk enumeration and chunk walk operations.SpacesPointerStatus: result of pointer validation.SpacesThreadExclusive: flags for thread-private default-chunk mode.SpacesErrorFn: callback type used with spaces_set_error_handler().SpacesErrorInfo
errorCode: the primary failure code.chunk: chunk where the problem was detected.argChunk, argPtr, argBuf, argSize, argCount, argFlags: call arguments associated with the failing operation.file, line: source location metadata when available.allocCount, passCount, checkpoint: allocator progress counters used by diagnostics.errorAlloc: allocation object associated with the failure when relevant.corruptAddr: address where corruption was found.objectCreationInfo: nested creation-site information when the library preserves it.threadID, pid: execution context of the report.callStack[32]: captured stack frames up to SPACES_MAX_CALL_STACK.SpacesChunkEntry
entry: current block address used by spaces_chunk_walk() as both cursor and output.chunk: owning chunk.type: block class flags.isInUse: non-zero for live allocations, zero for free entries.size: block size.lockCount: chunk lock depth visible during the walk.reserved_ptr, reserved_chunk: reserved fields.SpacesChunkInfo
chunk: identified chunk.type: currently present block categories.blockSizeFS: configured fixed-size block size.smallBlockSize: small-block routing threshold.pageSize: current sub-allocation page size.floor: configured minimum retained footprint.ceiling: configured maximum footprint.flags: chunk creation flags.errorFn: installed error handler.SpacesProcessInfo
growIncrement: process-wide growth quantum.freeBytes: retained free-byte threshold.largeBlockThreshold: size at which requests bypass the large-block heap.heapLimit: configured process-wide cap.heapTotal: total allocator-controlled OS memory.totalFree: aggregate reusable free space.useMunmap: whether address-space release via munmap is enabled.coelesceSystemAllocs: whether operating-system allocations may be coalesced across allocation boundaries.SPACES_FLAG_FIXED (0x0000): fixed allocation semantics marker.SPACES_FLAG_ZERO_INIT (0x0001): zero-fill newly allocated or newly grown bytes.SPACES_FLAG_MOVEABLE (0x0002): allow movement when the allocator compacts or resizes.SPACES_FLAG_RESIZEABLE (0x0004): reserve room that can improve later resize success.SPACES_FLAG_RESIZE_IN_PLACE (0x0008): fail instead of moving on resize.SPACES_FLAG_NO_GROW (0x0010): do not expand the chunk to satisfy the request.SPACES_FLAG_NO_EXTERNAL (0x0020): forbid direct OS-backed external blocks.SPACES_FLAG_NO_COMPACT (0x0040): disable compaction for the request path where applicable.SPACES_FLAG_NO_SERIALIZE (0x0080): bypass serialization for the specific operation or mode where supported.SPACES_CHUNK_SHARED (0x0001): shared-memory chunk.SPACES_CHUNK_SERIALIZE (0x0002): serialized chunk access.SPACES_CHUNK_VIRTUAL_LOCK (0x0004): request locked memory pages.SPACES_CHUNK_ZERO_INIT (0x0008): return zeroed memory from the chunk.SPACES_CHUNK_AREA (0x0010): chunk backed by caller-supplied area.SPACES_CHUNK_FILE_MAPPING (0x0020): file-mapped backing where supported.SPACES_CHUNK_DEFAULT (0x8000): create with default chunk characteristics.SPACES_BLOCK_FIXED (0x0001)SPACES_BLOCK_VAR_MOVEABLE (0x0002)SPACES_BLOCK_VAR_FIXED (0x0004)SPACES_BLOCK_EXTERNAL (0x0008)SPACES_BLOCK_FREE (0x0010)SPACES_SMALL_NONE: disable small-block acceleration.SPACES_SMALL_V3: heterogeneous-page small-block allocator with 1 byte overhead per block.SPACES_SMALL_V5: newer homogeneous-page allocator with zero per-block overhead and page recycling.SPACES_CHUNK_OK (1): current result is valid and iteration may continue.SPACES_CHUNK_CORRUPT (-1): corruption was found.SPACES_CHUNK_CORRUPT_FATAL (-2): fatal corruption was found.SPACES_CHUNK_END (0): iteration completed.SPACES_PTR_OK (1): valid live allocation.SPACES_PTR_WILD (0): not a recognized allocation base address.SPACES_PTR_FREE (-1): valid block, but it has already been freed.SPACES_THREAD_EXCLUSIVE_OFF (0): use the shared process default chunk.SPACES_THREAD_EXCLUSIVE_ON (0x10000): give the current thread a private default chunk.SPACES_FREE_ON_THREAD_TERM (0x20000): free thread-private default-chunk allocations automatically when the thread exits.spaces_default_chunk: current default chunk used by standard allocation wrappers.spaces_default_block_size: startup-time default fixed block size for the default chunk.spaces_default_page_size: startup-time default page size for the default chunk.spaces_default_flags: startup-time chunk creation flags for the default chunk.spaces_malloc_linked: non-zero when malloc replacement is linked in.SPACES_ERROR_RET: generic error sentinel cast from -1.SPACES_UNLOCK_FAILED: unlock failure sentinel.SPACES_MAX_CALL_STACK: maximum captured call-stack depth, currently 32.The following snippets cover the major API categories. Each can be compiled
directly against libspaces.a and spaces.h.
When linked with --whole-archive, malloc and free route through
the Spaces slab engine automatically.
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(void)
{
/* Ordinary malloc — backed by Spaces when linked in. */
double *v = malloc(1024 * sizeof *v);
for (int i = 0; i < 1024; i++)
v[i] = i * 0.5;
printf("v[100] = %.1f\n", v[100]);
free(v);
return 0;
}
Ideal for linked lists, trees, or any uniform-sized node pool. Zero per-allocation overhead and zero fragmentation.
#include <stdio.h>
#include "spaces.h"
typedef struct Node {
int key;
struct Node *left, *right;
} Node;
int main(void)
{
/* A chunk tuned for Node-sized allocations. */
SpacesChunk pool = spaces_chunk_create_fixed(sizeof(Node), 0, 0);
Node *root = spaces_chunk_alloc_fixed(pool);
root->key = 50;
root->left = spaces_chunk_alloc_fixed(pool);
root->left->key = 25;
root->left->left = root->left->right = NULL;
root->right = spaces_chunk_alloc_fixed(pool);
root->right->key = 75;
root->right->left = root->right->right = NULL;
printf("root=%d left=%d right=%d\n",
root->key, root->left->key, root->right->key);
/* Release the entire pool in one call — no per-node free needed. */
spaces_chunk_destroy(pool);
return 0;
}
#include <stdio.h>
#include <string.h>
#include "spaces.h"
int main(void)
{
SpacesChunk ch = spaces_chunk_create(0);
char *greeting = spaces_chunk_alloc(ch, 64, 0);
snprintf(greeting, 64, "Hello from a Spaces chunk");
printf("%s (usable: %zu bytes)\n",
greeting, spaces_chunk_ptr_size(greeting));
spaces_chunk_free(greeting);
spaces_chunk_destroy(ch);
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include "spaces.h"
int main(void)
{
SpacesChunk ch = spaces_chunk_create(0);
/* 4096-byte aligned buffer, useful for DMA or page-aligned I/O. */
void *buf = spaces_chunk_alloc_aligned(ch, 8192, 4096, 0);
printf("aligned ptr: %p (mod 4096 = %lu)\n",
buf, (unsigned long)((uintptr_t)buf % 4096));
spaces_chunk_free_aligned(buf);
spaces_chunk_destroy(ch);
return 0;
}
#include <stdio.h>
#include "spaces.h"
int main(void)
{
SpacesChunk ch = spaces_chunk_create(0);
for (int i = 0; i < 500; i++)
spaces_chunk_alloc(ch, 48 + (i % 200), 0);
printf("live allocations: %zu\n", spaces_chunk_count(ch));
printf("total bytes: %zu\n", spaces_chunk_size(ch));
spaces_chunk_shrink(ch);
printf("after shrink: %zu\n", spaces_chunk_size(ch));
spaces_chunk_destroy(ch);
return 0;
}
#include <stdio.h>
#include "spaces.h"
int main(void)
{
SpacesChunk ch = spaces_chunk_create(0);
for (int i = 0; i < 10; i++)
spaces_chunk_alloc(ch, 100, 0);
SpacesChunkEntry entry;
SpacesChunkStatus st = spaces_chunk_walk(ch, &entry);
int n = 0;
while (st == SPACES_CHUNK_OK) {
if (entry.isInUse)
n++;
st = spaces_chunk_walk(ch, &entry);
}
printf("walked %d in-use entries\n", n);
spaces_chunk_destroy(ch);
return 0;
}
#include <stdio.h>
#include "spaces.h"
int main(void)
{
SpacesChunk ch = spaces_chunk_create(SPACES_CHUNK_SERIALIZE);
/* Tune page size, growth, and ceiling. */
spaces_chunk_set_page_size(ch, 32768);
spaces_chunk_set_grow_increment(ch, 1024 * 1024);
spaces_chunk_set_ceiling(ch, 64 * 1024 * 1024);
spaces_chunk_set_free_bytes(ch, 512 * 1024);
spaces_chunk_set_small_block_size(ch, 512);
void *p = spaces_chunk_alloc(ch, 256, SPACES_FLAG_ZERO_INIT);
printf("alloc OK, ptr=%p\n", p);
spaces_chunk_free(p);
spaces_chunk_destroy(ch);
return 0;
}
#include <stdio.h>
#include "spaces.h"
static int my_handler(SpacesErrorInfo *err)
{
fprintf(stderr, "Spaces error %d\n", err->errorCode);
return 0; /* 0 = do not retry */
}
int main(void)
{
spaces_set_error_handler(my_handler);
spaces_init();
SpacesChunk ch = spaces_chunk_create(0);
spaces_chunk_set_ceiling(ch, 4096);
/* Allocate until the ceiling is hit. */
for (int i = 0; i < 1000; i++) {
void *p = spaces_chunk_alloc(ch, 64, 0);
if (!p) {
printf("allocation %d returned NULL (ceiling enforced)\n", i);
break;
}
}
spaces_chunk_destroy(ch);
spaces_shutdown();
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include "spaces.h"
static void *worker(void *arg)
{
int id = (int)(long)arg;
/* This thread will free everything it allocates. */
spaces_default_chunk_thread_exclusive(
SPACES_THREAD_EXCLUSIVE_ON | SPACES_FREE_ON_THREAD_TERM);
for (int i = 0; i < 10000; i++) {
void *p = malloc(64 + (i & 0xFF));
*(volatile char *)p = (char)i;
free(p);
}
printf("thread %d done\n", id);
return NULL;
}
int main(void)
{
spaces_init();
pthread_t t[4];
for (int i = 0; i < 4; i++)
pthread_create(&t[i], NULL, worker, (void *)(long)i);
for (int i = 0; i < 4; i++)
pthread_join(t[i], NULL);
spaces_shutdown();
return 0;
}
#include <stdio.h>
#include "spaces.h"
int main(void)
{
spaces_init();
spaces_process_set_grow_increment(2 * 1024 * 1024);
spaces_process_set_free_bytes(4 * 1024 * 1024);
spaces_process_set_heap_limit(256 * 1024 * 1024);
spaces_process_set_large_threshold(1024 * 1024);
SpacesProcessInfo info;
spaces_process_info(&info);
printf("grow increment: %zu\n", info.growIncrement);
printf("heap limit: %zu\n", info.heapLimit);
spaces_shutdown();
return 0;
}
#include <stdio.h>
#include <string.h>
#include "spaces.h"
int main(void)
{
SpacesChunk ch = spaces_chunk_create(0);
char *buf = spaces_chunk_alloc(ch, 32, 0);
strcpy(buf, "short");
/* Grow the buffer. */
buf = spaces_chunk_realloc(buf, 256, 0);
strcat(buf, " — now with room to spare");
printf("%s (size: %zu)\n", buf, spaces_chunk_ptr_size(buf));
spaces_chunk_free(buf);
spaces_chunk_destroy(ch);
return 0;
}
MIT — see LICENSE.
Copyright (c) 2021-2026 Praveen Vaddadi <thynktank@gmail.com>