@@ -5,10 +5,55 @@ reallocates it. The source of truth is currently a `DashMap` keyed by the
55allocation pointer. Looking up every freed pointer in that map adds overhead to
66the common case where the allocation was not sampled.
77
8- The allocation-prefix PoC makes sampled state cheap to query, but relocating
9- pointers requires a clean epoch after which every pointer passed to the custom
10- handlers has that prefix. This document describes two fallback alternatives
11- that preserve the pointer returned by ZendMM if that epoch cannot be guaranteed.
8+ A prefix makes sampled state cheap to query, but relocating pointers requires a
9+ clean epoch after which every pointer passed to the custom handlers has that
10+ prefix. All designs below preserve the pointer returned by ZendMM.
11+
12+ ## Shadow bitmap
13+
14+ ### Design
15+
16+ Use the allocation address as an exact index into a thread-local sparse bitmap.
17+ ZendMM pointers are at least eight-byte aligned, so each possible allocation
18+ start needs one bit. Within a four-kilobyte application page:
19+
20+ ``` text
21+ page = pointer >> 12
22+ slot = (pointer & 0xfff) >> 3
23+ word = slot >> 6
24+ mask = 1 << (slot & 63)
25+ ```
26+
27+ A page has 512 possible eight-byte-aligned starts, represented by eight ` u64 `
28+ words, or 64 bytes. A six-level radix tree keyed by ` page ` allocates those page
29+ bitmaps lazily and covers the full pointer address space without reserving a
30+ multi-terabyte flat shadow mapping.
31+
32+ After a sampled allocation is successfully inserted into the existing
33+ ` DashMap ` , set its shadow bit. On free or successful realloc, test and clear
34+ the bit. A clear bit skips the ` DashMap ` ; a set bit removes the known
35+ live sample. The pointer passed to the underlying allocator is never changed.
36+
37+ The bitmap is thread-local because ZendMM heaps are thread-local. Cross-thread
38+ freeing would already forward a pointer to the wrong ZendMM heap.
39+
40+ ### Benefits
41+
42+ * No pointer relocation or per-allocation size increase.
43+ * Pre-hook allocations naturally have clear bits.
44+ * The common free path uses address arithmetic and radix loads, without hashing.
45+ * No clean epoch, startup reset, or late-ZTS-thread hook is required.
46+ * The design does not depend on private ZendMM layouts.
47+ * It can filter the existing heap-live tracker on every supported PHP version.
48+
49+ ### Costs and open questions
50+
51+ * The first tracked address along a new radix path allocates metadata nodes.
52+ * Each application page containing a tracked allocation needs a 64-byte bitmap.
53+ * Empty radix leaves are currently retained until thread teardown. They can be
54+ pruned if long-lived heaps show unbounded metadata growth.
55+ * The ` DashMap ` remains the source of full ` LiveHeapSample ` metadata; the bitmap
56+ only makes its negative free-path lookup cheap.
1257
1358## Allocation footer
1459
@@ -157,17 +202,18 @@ when the expected map layout is unavailable.
157202
158203## Comparison
159204
160- | Property | Footer | Chunk-map filter |
161- | ---| ---| ---|
162- | Pointer relocation | None | None |
163- | Per-allocation memory | At least 8 bytes | None |
164- | Common free path | Block-size query and footer load | Page-map load |
165- | Private ZendMM layout | Debug trailer only | Chunk map and bin layout |
166- | Pre-hook allocations | Safe false positives | Naturally unmarked |
167- | Huge allocations | Supported with block size | Falls back to ` DashMap ` |
168- | Neighboring allocators | Requires fallback | Requires fallback |
169-
170- The footer is the simpler extension-owned design. The chunk-map filter has the
171- better expected hot-path and memory cost, but it carries substantially more
172- compatibility risk unless PHP exposes the required metadata through a supported
173- API.
205+ | Property | Shadow bitmap | Footer | Chunk-map filter |
206+ | ---| ---| ---| ---|
207+ | Pointer relocation | None | None | None |
208+ | Per-allocation memory | None | At least 8 bytes | None |
209+ | Common free path | Radix and bit lookup | Block size and footer | Page-map load |
210+ | Private ZendMM layout | None | Debug trailer only | Chunk map and bins |
211+ | Pre-hook allocations | Naturally unmarked | Safe false positives | Naturally unmarked |
212+ | Huge allocations | Supported | Supported | Falls back to ` DashMap ` |
213+ | Neighboring allocators | Supported | Requires fallback | Requires fallback |
214+
215+ The shadow bitmap is the preferred extension-owned design. The footer is
216+ simpler than modifying ZendMM internals but adds allocation overhead. The
217+ chunk-map filter has the cheapest expected hot path, but it carries
218+ substantially more compatibility risk unless PHP exposes the required metadata
219+ through a supported API.
0 commit comments