custom-allocators · git:20260905.fd53e99 · 2026-09-05 · sha256 94cd53ed18feb90c

custom-allocators git:20260905.fd53e99A

Immutable. This exact content is served forever at /api/v1/blob/94cd53ed18feb90c.

---
name: custom-allocators
description: 'Use when implementing pool/slab/arena allocators, tuning jemalloc/mimalloc/tcmalloc, writing a Rust GlobalAlloc, or benchmarking allocator performance and fragmentation.'
---

# Custom allocators

## Contract

| Field | Bound contract |
|---|---|
| Trigger | Memory allocator design, tuning, or benchmarking for C, Rust, or systems workloads. |
| Authority | Read-only. No source or remote mutation. Chat output only. |
| Side effect | Emits a structured guidance report to chat. |
| Done | The report names the allocator type, shows a pool or arena implementation, lists jemalloc/mimalloc/tcmalloc tuning options, shows a Rust GlobalAlloc pattern, and gives fragmentation and benchmarking steps. |

## Inputs

1. **Target language and allocator type** (required): C pool/arena, Rust GlobalAlloc, or tuning jemalloc/mimalloc/tcmalloc.
2. **Workload pattern** (required): allocation size distribution, object lifetime, thread count, and latency or throughput goal.
3. **Observed symptom** (optional): OOM, RSS growth, fragmentation, allocator contention, or unexpected latency.

## Procedure

1. **Classify the allocator type.** Match the workload to one of the allocator types below. Done when: the type is named.

   | Type | Best for | Allocation | Free |
   |---|---|---|---|
   | Pool/fixed-size | Fixed-size objects with a known maximum count | Constant time | Constant time |
   | Slab | Size-class caching, kernel-style caches | Constant time | Constant time |
   | Arena/bump | Request-scoped or frame-scoped allocations | Fast pointer bump | Bulk reset |
   | Buddy | Power-of-two blocks, large allocations | Split and merge by power of two | Coalesce |
   | General | jemalloc, mimalloc, tcmalloc | Variable time | Variable time |

2. **Build or review a pool allocator.** Use the C example below. Align backing memory to a cache line. Track the block size, block count, and a free list. Done when: the init, alloc, and free paths are shown.

   ```c
   #include <stddef.h>
   #include <stdint.h>
   #include <stdlib.h>

   typedef struct pool_block {
       struct pool_block *next;
   } pool_block_t;

   typedef struct {
       void   *memory;
       size_t  block_size;
       size_t  num_blocks;
       pool_block_t *free_list;
   } pool_t;

   int pool_init(pool_t *p, size_t block_size, size_t num_blocks) {
       p->block_size = block_size < sizeof(pool_block_t)
           ? sizeof(pool_block_t) : block_size;
       p->num_blocks = num_blocks;
       p->memory = aligned_alloc(64, p->block_size * num_blocks);
       if (!p->memory) return -1;
       p->free_list = NULL;
       for (size_t i = 0; i < num_blocks; i++) {
           pool_block_t *blk = (pool_block_t *)((char *)p->memory
               + i * p->block_size);
           blk->next = p->free_list;
           p->free_list = blk;
       }
       return 0;
   }

   void *pool_alloc(pool_t *p) {
       if (!p->free_list) return NULL;
       pool_block_t *blk = p->free_list;
       p->free_list = blk->next;
       return blk;
   }

   void pool_free(pool_t *p, void *ptr) {
       pool_block_t *blk = (pool_block_t *)ptr;
       blk->next = p->free_list;
       p->free_list = blk;
   }
   ```

3. **Build or review an arena allocator.** Use the C example below. Align each allocation. Reset the arena after the scope ends. Done when: the arena alloc and reset paths are shown.

   ```c
   typedef struct {
       char  *base;
       size_t capacity;
       size_t offset;
   } arena_t;

   void *arena_alloc(arena_t *a, size_t size, size_t align) {
       uintptr_t cur = (uintptr_t)(a->base + a->offset);
       uintptr_t aligned = (cur + align - 1) & ~(align - 1);
       size_t padding = aligned - cur;
       if (a->offset + padding + size > a->capacity)
           return NULL;
       a->offset += padding + size;
       return (void *)aligned;
   }

   void arena_reset(arena_t *a) { a->offset = 0; }
   ```

4. **Tune jemalloc.** Preload the jemalloc shared object and set `MALLOC_CONF`. Explain size classes, tcache, and arenas. Use `mallctl` to refresh allocator statistics or enable heap profiling. Done when: the tuning commands and concepts are listed.

   ```bash
   # Debian/Ubuntu example path; the exact name may differ on other distributions
   LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 ./myapp

   export MALLOC_CONF="background_thread:true,dirty_decay_ms:1000,muzzy_decay_ms:1000"

   # Profiling build of jemalloc: configure with --enable-prof
   export MALLOC_CONF="prof:true,prof_active:true,lg_prof_sample:19"

   # Print statistics on exit
   export MALLOC_CONF="stats_print:true"
   ```

5. **Tune mimalloc.** Preload the mimalloc shared object and set `MIMALLOC_SHOW_STATS` and `MIMALLOC_PAGE_RESET`. Explain the segment, page, and block hierarchy. Done when: the tuning options are listed.

   ```bash
   LD_PRELOAD=/usr/lib/libmimalloc.so ./myapp

   export MIMALLOC_SHOW_STATS=1
   export MIMALLOC_PAGE_RESET=1
   ```

6. **Tune tcmalloc.** Preload the tcmalloc shared object and set `TCMALLOC_SAMPLE_PARAMETER` for sampling. Explain per-thread caches and the central heap. Done when: the tuning options are listed.

   ```bash
   LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc.so.4 ./myapp

   export TCMALLOC_SAMPLE_PARAMETER=524288
   ```

7. **Implement Rust GlobalAlloc.** Implement the `GlobalAlloc` trait, handle `Layout` correctly, and register the allocator with `#[global_allocator]`. Track allocated bytes with atomics if needed. For `no_std`, use the `alloc` crate without `System`. Done when: the trait implementation and registration are shown.

   ```rust
   use std::alloc::{GlobalAlloc, Layout, System};
   use std::sync::atomic::{AtomicUsize, Ordering};

   struct TrackingAllocator;

   static ALLOCATED: AtomicUsize = AtomicUsize::new(0);

   unsafe impl GlobalAlloc for TrackingAllocator {
       unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
           let ptr = System.alloc(layout);
           if !ptr.is_null() {
               ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed);
           }
           ptr
       }
       unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
           System.dealloc(ptr, layout);
           ALLOCATED.fetch_sub(layout.size(), Ordering::Relaxed);
       }
   }

   #[global_allocator]
   static GLOBAL: TrackingAllocator = TrackingAllocator;
   ```

8. **Measure fragmentation.** Distinguish internal fragmentation from external fragmentation. Read allocator statistics and compare RSS to allocated bytes. Done when: the metrics are listed.

   | Type | Definition | Detection |
   |---|---|---|
   | Internal | Allocated block is larger than requested | Allocator stats; size class rounding |
   | External | Free memory is not usable for a request | `mallinfo` or `malloc_info`; RSS minus heap |

9. **Benchmark the allocator.** Compare single-thread alloc and free, multi-thread contention, and a mixed size distribution. Use `perf stat` or a microbenchmark harness. Done when: the benchmark design is listed.

## Failure and recovery

| Failure class | Behavior |
|---|---|
| Pool alloc returns NULL | Increase the pool size or check for leaks. |
| jemalloc RSS does not drop | Lower `dirty_decay_ms` or call `madvise` where appropriate. |
| Arena OOM | Reset the arena between phases or chain multiple arenas. |
| Rust allocator undefined behavior | Store the size and alignment with each allocation and pass the same `Layout` to `dealloc`. |
| Worse performance with mimalloc | Benchmark the workload against jemalloc and select the better fit. |
| High external fragmentation | Segregate allocations by lifetime or use pools for long-lived mixed sizes. |

## Output

1. The chosen allocator type and the matching workload pattern.
2. A pool or arena implementation, or tuning commands for jemalloc/mimalloc/tcmalloc.
3. A Rust `GlobalAlloc` example when Rust is the target.
4. Fragmentation metrics and a benchmark plan.