2323
2424## What it does
2525
26- ` mod-alloc ` is a global-allocator wrapper that tracks every
27- allocation and deallocation. It answers:
26+ ` mod-alloc ` is a ` #[global_allocator] ` wrapper that tracks every
27+ allocation and answers four questions for the code that runs while
28+ it is installed:
2829
2930- ** How many allocations did this code path make?**
30- - ** How many total bytes were allocated ?**
31+ - ** How many total bytes did it allocate ?**
3132- ** What was the peak resident memory?**
32- - ** Which call-sites caused the most allocations?** (with ` backtraces ` feature)
33+ - ** Which call sites did most of the allocating?** (with the
34+ ` backtraces ` feature)
3335
34- Designed as a lean replacement for ` dhat ` with:
35-
36- - ** MSRV 1.75** (vs dhat's 1.85+)
37- - ** Zero external dependencies** in the hot path (no ` backtrace ` crate)
38- - ** Lower overhead** per allocation via purpose-built inline capture
39- - ** DHAT-compatible output** so existing viewer tools work (via the ` dhat-compat ` feature)
36+ The whole crate is ` std ` -only. No ` backtrace ` , no ` addr2line ` , no
37+ ` gimli ` , no ` libc ` . Inline frame-pointer walking on ` x86_64 ` and
38+ ` aarch64 ` for call-site capture; raw ` mmap ` / ` VirtualAlloc ` for
39+ the per-thread arena and the global aggregation table.
4040
4141## Quick start
4242
@@ -49,7 +49,7 @@ static GLOBAL: ModAlloc = ModAlloc::new();
4949fn main () {
5050 let p = Profiler :: start ();
5151
52- let v : Vec <u64 > = (0 .. 1000 ). collect ();
52+ let v : Vec <u64 > = (0 .. 1_000 ). collect ();
5353 drop (v );
5454
5555 let stats = p . stop ();
@@ -63,36 +63,98 @@ fn main() {
6363
6464``` toml
6565[dependencies ]
66- mod-alloc = " 0.9" # counters only (default)
67- mod-alloc = { version = " 0.9" , features = [" backtraces" ] } # + call-site capture (lands in v0.9.1)
68- mod-alloc = { version = " 0.9" , features = [" dhat-compat" ] } # + DHAT-format output (lands in v0.9.3)
66+ mod-alloc = " 0.9" # Tier 1: counters (default)
67+ mod-alloc = { version = " 0.9" , features = [" backtraces" ] } # Tier 2: call-site capture
68+ mod-alloc = { version = " 0.9" , features = [" dhat-compat" ] } # Tier 3: DHAT JSON output (v0.9.3)
69+ ```
70+
71+ | Feature | What it adds | Status |
72+ | ---------------| ---------------------------------------------------------| --------|
73+ | ` counters ` | Four lock-free counters via ` GlobalAlloc ` (default) | shipped (v0.9.0) |
74+ | ` backtraces ` | Inline FP walk + per-call-site aggregation | shipped (v0.9.1) |
75+ | ` dhat-compat ` | Emit JSON for the official DHAT viewer | planned (v0.9.3) |
76+
77+ ## Backtraces
78+
79+ Enabling the ` backtraces ` feature requires frame pointers in the
80+ caller's build:
81+
82+ ``` toml
83+ # .cargo/config.toml
84+ [build ]
85+ rustflags = [" -C" , " force-frame-pointers=yes" ]
86+ ```
87+
88+ The crate's ` build.rs ` emits a ` cargo:warning= ` at compile time if
89+ ` RUSTFLAGS ` is missing this. Without it the walker degrades
90+ gracefully (returns shallow or empty traces) but does not crash.
91+
92+ The aggregation-table size is configurable at process start:
93+
94+ ``` bash
95+ MOD_ALLOC_BUCKETS=16384 ./your-binary
6996```
7097
98+ Default is 4,096 buckets (~ 384 KB). Range ` [64, 1_048_576] ` ,
99+ rounded up to the next power of two.
100+
101+ ## Performance
102+
103+ Measured per allocation, end to end, on a Windows x86_64 dev host
104+ with ` cargo run --release --example bench_overhead ` :
105+
106+ | Build | Per alloc + dealloc cycle |
107+ | ----------------------------------------| --------------------------:|
108+ | Tier 1 only (` counters ` , default) | ** 34.9 ns** |
109+ | Tier 1 + Tier 2 (` backtraces ` ) | ** ~ 1,950 ns** |
110+
111+ Tier 1 comes in well under the 50 ns target from the spec
112+ ([ ` REPS.md ` ] ( REPS.md ) section 6). Tier 2 is currently above the
113+ 200 ns target in that section; closing that gap is tracked for
114+ v0.9.1.1. The Tier 2 path is correct and recursion-safe in the
115+ current release; the optimisation is a separate, focused pass.
116+
71117## Why a new allocation profiler
72118
73- ` dhat ` is the de-facto standard but its dependency chain
74- (` backtrace ` 0.3.76 → ` addr2line ` 0.25.1) locks consumers at Rust
75- 1.85. For projects with broader MSRV targets, this is a real cost.
119+ ` dhat ` is the de facto standard for allocation profiling in Rust,
120+ but its dependency chain (` backtrace 0.3.76 ` → ` addr2line 0.25.1 ` )
121+ forces consumers to MSRV ` 1.85 ` +. For projects with a broader MSRV
122+ target, that cost is real.
76123
77- ` mod-alloc ` provides the same core capability with inline backtrace
78- capture (frame-pointer-based, x86_64 + aarch64 initially) and no
79- external dependencies. The trade: fewer architectures supported in
80- v1.0; we add ARM32, RISC-V, etc. based on demand.
124+ ` mod-alloc ` provides the same core capability with inline
125+ backtrace capture (frame-pointer-based, ` x86_64 ` + ` aarch64 ` ) and
126+ no external dependencies. The trade-off is fewer architectures
127+ supported in ` 1.0 ` ; ARM32, RISC-V, and others land based on
128+ demand.
81129
82130## Status
83131
84- ` v0.9.0 ` ships Tier 1 (counters). Installing ` ModAlloc ` as
85- ` #[global_allocator] ` tracks every allocation, deallocation,
86- reallocation, and zero-init allocation against four lock-free
87- atomic counters. Per-allocation overhead measures under 50 ns on
88- x86_64 (` cargo run --release --example bench_overhead ` ). Tier 2
89- (inline backtrace capture) lands in ` v0.9.1 ` . Tier 3
90- (DHAT-compatible JSON output) lands in ` v0.9.3 ` . The ` 1.0 ` release
91- freezes the public API and the wire format.
132+ | Milestone | Version | State |
133+ | --------------------------------------------| ----------| ----------|
134+ | Name-claim placeholder | ` v0.1.0 ` | shipped |
135+ | Real ` GlobalAlloc ` + Tier 1 counters | ` v0.9.0 ` | shipped |
136+ | Tier 2: inline backtrace capture | ` v0.9.1 ` | shipped |
137+ | Tier 2 perf optimisation | ` v0.9.1.1 ` | planned |
138+ | Symbolication for reports | ` v0.9.2 ` | planned |
139+ | Tier 3: DHAT-compatible JSON output | ` v0.9.3 ` | planned |
140+ | ` dev-bench ` integration (drop dhat) | ` v0.9.4 ` | planned |
141+ | Stable API (` 1.0 ` ) | ` v1.0.0 ` | planned |
142+
143+ The ` 1.0 ` release freezes the public API and the wire format.
144+ Breaking changes after that require a major bump.
145+
146+ ## Out of scope
147+
148+ - Replacing the system allocator. Use ` mimalloc ` or
149+ ` jemallocator ` for that.
150+ - Use-after-free / double-free detection. Use AddressSanitizer.
151+ - Source-level instrumentation (build.rs, proc macros). The one
152+ build.rs in this crate exists solely to detect missing frame
153+ pointers at compile time.
92154
93155## Minimum supported Rust version
94156
95- ` 1.75 ` , pinned in ` Cargo.toml ` and verified by CI.
157+ ` 1.75 ` , pinned in ` Cargo.toml ` and verified by CI on every push .
96158
97159## License
98160
@@ -106,4 +168,4 @@ Apache-2.0. See [LICENSE](LICENSE).
106168 <br >
107169 <h2 ></h2 >
108170 Copyright © ; 2026 James Gober.
109- </div >
171+ </div >
0 commit comments