nexus-slab 2.3.4

A high-performance slab allocator optimized for predictable tail latency
Documentation

nexus-slab

Manual memory management with SLUB-style slab allocation. 1 cycle churn (alloc+free) at 32B, sub-cycle free. 15x faster than Box. Placement new confirmed in assembly.

Why

When you churn same-type objects (orders, connections, timers, nodes), the global allocator is your bottleneck. malloc/free contend with every other allocation in the process. A slab gives you:

  • LIFO cache locality — free a slot, allocate again, get the same cache-hot memory back
  • Zero fragmentation — every slot is the same size
  • Allocator isolation — your hot path doesn't compete with logging or serialization
  • Placement new — values written directly into slot memory, no copy

Quick Start

use nexus_slab::bounded::Slab;

// SAFETY: caller accepts manual memory management contract
let slab = unsafe { Slab::with_capacity(1024) };

let mut ptr = slab.alloc(Order { id: 1, price: 100.5 });
ptr.price = 105.0;         // safe Deref/DerefMut
slab.free(ptr);             // consumes handle, can't use after

Construction is unsafe — you're opting into:

  • Free everything you allocate. Unfree'd slots leak.
  • Free from the same slab. Cross-slab free corrupts the freelist.
  • Don't share across threads. The slab is !Send/!Sync.

Everything after construction is safe. Slot<T> is move-only (!Copy, !Clone) — the compiler prevents double-free.

API

Typed Slabs

use nexus_slab::bounded::Slab;   // fixed capacity
use nexus_slab::unbounded::Slab; // grows via chunks

// Bounded
let slab = unsafe { Slab::with_capacity(1024) };
let ptr = slab.alloc(42u64);           // panics if full
let ptr = slab.try_alloc(42u64)?;      // returns Err(Full(42)) if full
let value = slab.take(ptr);            // extract without drop, free slot
slab.free(ptr);                        // drop value, free slot

// Unbounded
let slab = unsafe { Slab::with_chunk_capacity(256) };
let ptr = slab.alloc(42u64);           // never fails, grows if needed

// Placement new (two-phase)
if let Some(claim) = slab.claim() {
    let ptr = claim.write(Order { id: 1, price: 100.5 });
    // value constructed directly in slot memory
    slab.free(ptr);
}

Byte Slabs (Type-Erased)

Store heterogeneous types in one slab. Any T fitting in N bytes works.

use nexus_slab::byte::bounded::Slab;

let slab: Slab<128> = unsafe { Slab::with_capacity(64) };

let p1 = slab.alloc(42u64);                    // 8 bytes
let p2 = slab.alloc([1.0f64; 8]);              // 64 bytes
let p3 = slab.alloc(String::from("hello"));    // different types, same slab

slab.free(p3);
slab.free(p2);
slab.free(p1);

Slot

8-byte move-only handle. Safe Deref/DerefMut access.

let mut ptr = slab.alloc(Order { id: 1, price: 100.5 });

// Safe access
ptr.price = 105.0;
let p: Pin<&mut Order> = ptr.pin_mut();  // stable address, no Unpin needed

// Raw pointer escape hatch
let raw = ptr.into_raw();               // disarms debug leak detector
let ptr = unsafe { Slot::from_raw(raw) };  // reconstruct
slab.free(ptr);

Debug mode: dropping a Slot without calling free() or take() panics (leak detection). Release mode: silent leak.

Rc Slabs (Shared Ownership)

When multiple owners need access to the same slot — e.g., a collection holds a node and the user holds a handle for cancellation.

use nexus_slab::rc::bounded::Slab;

// SAFETY: caller accepts manual memory management contract
let slab = unsafe { Slab::<Order>::with_capacity(1024) };

// Alloc returns RcSlot<Order> with refcount 1
let h1 = slab.alloc(Order { id: 1, price: 100.0 });

// Clone is safe — increments refcount
let h2 = h1.clone();  // refcount 2

// Access through borrow guards — one borrow at a time
{
    let mut order = h1.borrow_mut();  // exclusive guard
    order.price = 105.0;
}
{
    let order = h2.borrow();          // shared guard (still exclusive — see below)
    assert_eq!(order.price, 105.0);   // mutation visible across clones
}

// Every handle must be freed — slot deallocated on last free
slab.free(h2);  // refcount 2 → 1, slot stays alive
slab.free(h1);  // refcount 1 → 0, value dropped, slot freed

Borrow rules: More conservative than RefCell. Only one borrow (shared OR exclusive) is allowed at a time. Any attempt to borrow while another borrow is active panics. This is intentional — shared mutable state in a low-latency system should be tightly controlled.

let h1 = slab.alloc(42u64);
let h2 = h1.clone();

let _g1 = h1.borrow();
let _g2 = h2.borrow();  // PANICS — already borrowed (even though both are shared)

Pin support: Slab memory never moves, so Pin is sound without T: Unpin:

let mut pinned = handle.pin_mut();  // Pin<RefMut<'_, T>>

Performance

See BENCHMARKS.md for full methodology and numbers.

Pinned to core 0. Batched timing (64 ops per rdtsc pair), 10K samples.

Churn — alloc + deref + free (cycles p50)

Size Slab Box Speedup
32B 1 15 15x
64B 2 17 8.5x
128B 4 23 5.8x
256B 7 29 4.1x
512B 14 44 3.1x
1024B 25 103 4.1x
4096B 78 249 3.2x

Free (cycles p50)

Size Slab Box
32B-4096B 0-1 23-26

Slab free is sub-cycle regardless of size — a single freelist pointer write. Box free is constant at ~24 cycles (allocator bookkeeping).

Assembly-verified placement new: alloc() compiles to freelist pop + SIMD store directly into slot memory. No intermediate copy.

Bounded vs Unbounded

Bounded Unbounded
Capacity Fixed at init Grows via chunks
Full behavior Err(Full) Always succeeds
Alloc latency ~2 cycles ~2 cycles (LIFO from current chunk)
Growth Never New chunk (~40 cycle p999)

Use bounded when you know your capacity. Use unbounded when you need overflow headroom without crashing.

Architecture

Pointer Provenance

Freelist pointers are derived from UnsafeCell for correct write provenance under stacked borrows. This ensures that miri accepts the freelist manipulation as valid -- pointers written into vacant slots carry the correct provenance tag from the UnsafeCell wrapping the union, not from a stale read-only reference.

SlotCell (SLUB-style union)

#[repr(C)]
union SlotCell<T> {
    next_free: *mut SlotCell<T>,  // vacant: freelist link
    value: ManuallyDrop<MaybeUninit<T>>,  // occupied: user data
}

No tag, no metadata. Writing a value overwrites the freelist pointer. The Slot handle is the proof of occupancy.

Unbounded Builder

use nexus_slab::unbounded::Builder;

// SAFETY: caller guarantees slab contract
let slab = unsafe {
    Builder::new()
        .chunk_capacity(4096)
        .initial_chunks(4)
        .build::<Order>()
};

Features

Feature Default Description
std yes Enables alloc + thread::panicking() for debug leak detection
alloc with std Vec-backed storage. Required for slab operation.
rc no Reference-counted handles (rc::bounded::Slab, rc::unbounded::Slab) with borrow guards

no_std with alloc is supported for embedded systems.

License

MIT OR Apache-2.0