Skip to content

Commit 8f10bed

Browse files
allocator stats inline + mremap XL fast path + bench harness honesty
Stats accounting moved out of the Allocator.c _dyn dispatch wrappers and into each typed backend body (Heap, Page, Arena, Slab, Budget, Debug). The dyn wrappers no longer carry parallel updates -- duplicate writes would double-count. Reads use new direct accessor macros in <Misra/Std/Allocator.h>: AllocatorBytesInUse(a), AllocatorBytesRequested(a), AllocatorPeakBytesInUse(a), AllocatorAllocations(a), AllocatorReallocations(a), AllocatorDeallocations(a), AllocatorFailedAllocations(a) Same `((void)0, ptr->field)` shape as BufLength / VecCapacity. No AllocatorGetStats(...) function call, no _Generic-dispatched reader. ALLOCATOR_OF(a) does the typed -> base cast at the macro level so each read compiles down to a single field load. AllocatorResetStats stays a function (it mutates). Heap remap gains an mremap fast path on the XL (mmap-per-alloc) class. On Linux direct-syscall builds, os_page_remap calls mremap with MREMAP_MAYMOVE and the xl[] descriptor gets re-inserted at the new sorted position. Darwin / Windows have no equivalent; those fall back to alloc-new + memcpy. Drops BM_ReallocGrow on heap-based misra columns from 247 us to 16 us (15x speedup), matching mimalloc and tcmalloc. Bench harness corrections: - Allocator_misra_correct.c picks PageAllocator above 4 KiB instead of falling back to HeapAllocator -- the latter was the "wrong tool" path that misra-correct is supposed to avoid. AllocFreePair at 16/64 KiB drops 2400 ns -> 17 ns (matches misra-page). - BM_ArenaBumpReset now SkipWithMessage("backend has no bulk reset") for non-arena backends instead of duplicating BM_BatchAllocFree numbers in the row. Honest n/a. - Benchmark/meson.build documents the LTO requirement: cross-TU inlining of the trivial bench_alloc / bench_free / bench_realloc wrappers into Bench.cpp's hot loop relies on -Db_lto=true. README reproduce block already shows both setup invocations. - Benchmark/README.md regenerated with stats inline + LTO; misra columns now report bytes_in_use in the fragmentation table for the first time (lowest values in the table, exact live MB). CODING-CONVENTIONS.md: replaced the "typed-direct skips stats" rule (now wrong) with three general patterns -- shared per-instance state on the base, one writer layer only, read-only fields exposed through the ((void)0, ptr->field) accessor macro shape. Windows test runner: SetJmp / LongJmp asm pair gated on __clang__ so plain MSVC cl.exe falls back to libc setjmp/longjmp -- cl doesn't understand __attribute__((naked)) or GNU-style __asm__. clang-cl path unchanged.
1 parent af143d4 commit 8f10bed

20 files changed

Lines changed: 608 additions & 322 deletions

Benchmark/README.md

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ Single alloc/free pair, 16 B:
1010

1111
| backend | time |
1212
|---|---:|
13-
| tcmalloc | 3.5 ns |
14-
| glibc | 7.4 ns |
15-
| jemalloc | 3.9 ns |
16-
| mimalloc | 3.2 ns |
13+
| tcmalloc | 3.4 ns |
14+
| glibc | 7.2 ns |
15+
| jemalloc | 3.4 ns |
16+
| mimalloc | 2.9 ns |
1717
| misra (Heap, validate-full) | 13.4 ns |
18-
| misra (Heap, validate-fast) | 13.6 ns |
19-
| misra-correct (Slab, validate-full) | 12.4 ns |
20-
| misra-correct (Slab, validate-fast) | 7.0 ns |
18+
| misra (Heap, validate-fast) | 13.0 ns |
19+
| misra-correct (Slab, validate-full) | 12.8 ns |
20+
| misra-correct (Slab, validate-fast) | 8.8 ns |
2121

2222
## Timing
2323

@@ -27,13 +27,13 @@ One `alloc(size)` immediately followed by `free(ptr)`, repeated. Hot reuse — t
2727

2828
| benchmark | glibc | jemalloc | mimalloc | tcmalloc | misra-full | misra-fast | misra-correct-full | misra-correct-fast | misra-arena | misra-page |
2929
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
30-
| 16 B | 7.4 | 3.9 | 3.2 | 3.5 | 13.4 | 13.6 | 12.4 | 7.0 | 9.5 | n/a |
31-
| 64 B | 7.2 | 3.9 | 3.3 | 3.5 | 13.2 | 13.3 | 12.4 | 6.9 | 9.3 | n/a |
32-
| 256 B | 7.2 | 4.3 | 14.0 | 3.5 | 13.2 | 13.6 | 12.4 | 7.0 | 9.3 | n/a |
33-
| 1 KiB | 22.1 | 6.0 | 15.5 | 3.5 | 13.4 | 13.4 | 12.5 | 7.0 | 9.3 | n/a |
34-
| 4 KiB | 15.2 | 12.4 | 11.1 | 3.7 | 2481.2 | 2445.8 | 12.2 | 6.8 | 9.3 | 17.0 |
35-
| 16 KiB | 16.2 | 20.8 | 20.2 | 3.7 | 1214.3 | 1005.3 | 2463.5 | 2389.7 | 9.2 | 16.9 |
36-
| 64 KiB | 17.4 | 288.3 | 20.3 | 3.8 | 1201.8 | 1017.4 | 2496.4 | 2380.8 | 9.2 | 16.9 |
30+
| 16 B | 7.2 | 3.4 | 2.9 | 3.4 | 13.4 | 13.0 | 12.8 | 8.8 | 9.9 | n/a |
31+
| 64 B | 7.0 | 3.5 | 3.0 | 3.3 | 13.5 | 13.3 | 12.7 | 8.7 | 9.9 | n/a |
32+
| 256 B | 7.2 | 3.9 | 13.5 | 3.3 | 13.7 | 13.3 | 12.7 | 8.7 | 9.9 | n/a |
33+
| 1 KiB | 7.2 | 5.2 | 14.8 | 3.3 | 13.2 | 13.2 | 12.7 | 8.7 | 9.9 | n/a |
34+
| 4 KiB | 22.9 | 9.7 | 10.6 | 3.3 | 1514.5 | 1517.9 | 12.7 | 8.7 | 9.9 | 16.6 |
35+
| 16 KiB | 23.4 | 18.7 | 19.9 | 3.4 | 1159.8 | 1152.9 | 16.9 | 16.9 | 9.9 | 16.7 |
36+
| 64 KiB | 25.6 | 270.3 | 20.0 | 3.4 | 1529.7 | 1531.6 | 16.9 | 16.9 | 9.9 | 16.8 |
3737

3838
_Values in ns._
3939

@@ -43,9 +43,9 @@ _Values in ns._
4343

4444
| benchmark | glibc | jemalloc | mimalloc | tcmalloc | misra-full | misra-fast | misra-correct-full | misra-correct-fast | misra-arena | misra-page |
4545
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
46-
| 128 × 64 B | 1.4 | 1.1 | 0.4 | 0.5 | 1.4 | 1.4 | 1.7 | 1.0 | n/a | n/a |
47-
| 1024 × 64 B | 12.3 | 12.0 | 3.6 | 4.0 | 11.2 | 10.9 | 18.7 | 12.9 | n/a | n/a |
48-
| 8192 × 64 B | 101.2 | 95.5 | 34.4 | 39.8 | 87.6 | 86.7 | 419.4 | 361.1 | n/a | n/a |
46+
| 128 × 64 B | 1.2 | 1.0 | 0.5 | 0.5 | 1.5 | 1.4 | 1.7 | 1.0 | n/a | n/a |
47+
| 1024 × 64 B | 9.6 | 10.9 | 4.0 | 4.3 | 11.8 | 11.7 | 17.5 | 12.2 | n/a | n/a |
48+
| 8192 × 64 B | 77.9 | 88.9 | 34.3 | 39.1 | 94.7 | 94.6 | 390.2 | 358.8 | n/a | n/a |
4949

5050
_Values in us._
5151

@@ -55,9 +55,9 @@ Same shape as the pair test but writes every byte of the allocation before freei
5555

5656
| benchmark | glibc | jemalloc | mimalloc | tcmalloc | misra-full | misra-fast | misra-correct-full | misra-correct-fast | misra-arena | misra-page |
5757
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
58-
| 64 B | 7.9 | 5.2 | 3.3 | 4.6 | 15.1 | 14.3 | 14.8 | 9.3 | 11.6 | n/a |
59-
| 4 KiB | 36.7 | 29.4 | 25.5 | 19.5 | 3377.2 | 2435.7 | 31.9 | 26.2 | 52.2 | 35.3 |
60-
| 64 KiB | 973.6 | 1418.4 | 866.3 | 841.0 | 13475.6 | 13614.5 | 14605.9 | 14411.7 | 914.2 | 847.3 |
58+
| 64 B | 7.0 | 4.3 | 3.3 | 4.1 | 14.4 | 14.3 | 14.4 | 9.9 | 10.7 | n/a |
59+
| 4 KiB | 33.0 | 23.1 | 24.1 | 18.8 | 2348.6 | 2364.9 | 30.9 | 25.0 | 56.3 | 33.7 |
60+
| 64 KiB | 864.7 | 1063.5 | 865.2 | 839.8 | 13202.5 | 13183.3 | 847.7 | 847.2 | 949.9 | 847.0 |
6161

6262
_Values in ns._
6363

@@ -67,7 +67,7 @@ _Values in ns._
6767

6868
| benchmark | glibc | jemalloc | mimalloc | tcmalloc | misra-full | misra-fast | misra-correct-full | misra-correct-fast | misra-arena | misra-page |
6969
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
70-
| Pareto(1.16, 24) | 31.1 | 12.0 | 11.4 | 11.5 | 32.5 | 31.3 | 33.3 | 32.5 | n/a | n/a |
70+
| Pareto(1.16, 24) | 28.6 | 10.9 | 11.3 | 11.1 | 32.5 | 32.6 | 32.2 | 31.8 | n/a | n/a |
7171

7272
_Values in us._
7373

@@ -77,7 +77,7 @@ Geometric realloc ladder from 8 B up to 1 MiB. Time per full ladder, lower is be
7777

7878
| benchmark | glibc | jemalloc | mimalloc | tcmalloc | misra-full | misra-fast | misra-correct-full | misra-correct-fast | misra-arena | misra-page |
7979
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
80-
| 8 B → 1 MiB | 4614.3 | 2825.7 | 18236.5 | 15248.1 | 247616.4 | 249123.4 | 247637.3 | 245527.6 | 114.4 | n/a |
80+
| 8 B → 1 MiB | 8265.9 | 4054.7 | 18185.7 | 15349.6 | 15787.3 | 16312.1 | 16030.5 | 16180.3 | 88.3 | n/a |
8181

8282
_Values in ns._
8383

@@ -87,9 +87,9 @@ Allocate N small (32 B) objects, then release them all. Arena does this as one O
8787

8888
| benchmark | glibc | jemalloc | mimalloc | tcmalloc | misra-full | misra-fast | misra-correct-full | misra-correct-fast | misra-arena | misra-page |
8989
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
90-
| 128 × 32 B | 1.5 | 0.6 | 0.4 | 0.5 | 1.4 | 1.3 | 1.4 | 1.4 | 0.7 | n/a |
91-
| 1024 × 32 B | 12.8 | 10.2 | 3.3 | 4.1 | 11.3 | 10.6 | 11.3 | 11.3 | 5.3 | n/a |
92-
| 8192 × 32 B | 100.2 | 101.7 | 31.1 | 32.8 | 91.1 | 84.9 | 89.8 | 88.3 | 42.5 | n/a |
90+
| 128 × 32 B | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | 0.6 | n/a |
91+
| 1024 × 32 B | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | 4.9 | n/a |
92+
| 8192 × 32 B | n/a | n/a | n/a | n/a | n/a | n/a | n/a | n/a | 39.5 | n/a |
9393

9494
_Values in us._
9595

@@ -99,11 +99,11 @@ Each allocator's own introspection API reports committed bytes after the workloa
9999

100100
| benchmark | live MB | glibc MB | jemalloc MB | mimalloc MB | tcmalloc MB | misra-full MB | misra-fast MB | misra-correct-full MB | misra-correct-fast MB | misra-arena MB | misra-page MB |
101101
|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|
102-
| Checkerboard (4 K small) | 0.6 | 2.4 | 9.2 | n/a | 7.0 | n/a | n/a | n/a | n/a | n/a | n/a |
103-
| Checkerboard (16 K small) | 2.5 | 4.1 | 11.9 | n/a | 7.0 | n/a | n/a | n/a | n/a | n/a | n/a |
104-
| Checkerboard (64 K small) | 10.0 | 15.3 | 21.8 | n/a | 16.0 | n/a | n/a | n/a | n/a | n/a | n/a |
105-
| Lifetime mix | 4.0 | 5.1 | 19.0 | n/a | 16.0 | n/a | n/a | n/a | n/a | n/a | n/a |
106-
| Page overhang | 18.2 | 20.5 | 32.3 | n/a | 29.0 | n/a | n/a | n/a | n/a | n/a | n/a |
102+
| Checkerboard (4 K small) | 0.6 | 1.1 | 9.2 | n/a | 2.0 | 0.6 | 0.6 | n/a | n/a | n/a | n/a |
103+
| Checkerboard (16 K small) | 2.5 | 3.9 | 11.8 | n/a | 4.0 | 2.5 | 2.5 | n/a | n/a | n/a | n/a |
104+
| Checkerboard (64 K small) | 10.0 | 15.1 | 21.8 | n/a | 14.0 | 10.0 | 10.0 | n/a | n/a | n/a | n/a |
105+
| Lifetime mix | 4.0 | 4.9 | 20.2 | n/a | 14.0 | 4.0 | 4.0 | n/a | n/a | n/a | n/a |
106+
| Page overhang | 18.2 | 20.3 | 32.4 | n/a | 27.0 | 18.2 | 18.2 | n/a | n/a | n/a | n/a |
107107

108108
## How to read
109109

@@ -143,10 +143,10 @@ Regenerates this file with measurements from the host.
143143

144144
| | |
145145
|---|---|
146-
| timestamp | 2026-05-29 00:47:52 UTC |
147-
| git rev | db0e77fcc603 (master) |
146+
| timestamp | 2026-05-29 02:14:47 UTC |
147+
| git rev | af143d4679e6 (master) |
148148
| host CPU | Intel(R) Core(TM) Ultra 7 165U |
149149
| kernel | Linux 6.18.28 |
150150
| compiler | gcc 14.3.0 |
151-
| build | validate-full: buildtype=release optimization=3 b_sanitize=[] b_lto=False alloc_debug=False heap_validate_full=True | validate-fast: buildtype=release optimization=3 b_lto=False b_sanitize=[] alloc_debug=False heap_validate_full=False |
151+
| build | validate-full: buildtype=release optimization=3 b_lto=True b_sanitize=[] alloc_debug=False heap_validate_full=True | validate-fast: buildtype=release optimization=3 b_lto=True b_sanitize=[] alloc_debug=False heap_validate_full=False |
152152
| reps | 10 per row (median reported) |

Benchmark/Source/Allocator_misra.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,7 @@ void bench_free(void *p) {
9292

9393
uint64_t bench_live_bytes(void) {
9494
#if FEATURE_ALLOC_STATS
95-
AllocatorStats s = AllocatorGetStats(g_alloc);
96-
return (uint64_t)s.bytes_in_use;
95+
return (uint64_t)AllocatorBytesInUse(g_alloc);
9796
#else
9897
return 0;
9998
#endif

Benchmark/Source/Allocator_misra_arena.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,7 @@ void bench_free(void *p) {
8989

9090
uint64_t bench_live_bytes(void) {
9191
#if FEATURE_ALLOC_STATS
92-
AllocatorStats s = AllocatorGetStats(ALLOCATOR_OF(&g_arena));
93-
return (uint64_t)s.bytes_in_use;
92+
return (uint64_t)AllocatorBytesInUse(&g_arena);
9493
#else
9594
return 0;
9695
#endif

Benchmark/Source/Allocator_misra_correct.c

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,23 @@
2424

2525
#include <Misra/Std/Allocator.h>
2626
#include <Misra/Std/Allocator/Heap.h>
27+
#include <Misra/Std/Allocator/Page.h>
2728
#include <Misra/Std/Allocator/Slab.h>
2829

29-
// MODE_NONE = pre-init / post-teardown. MODE_HEAP / MODE_SLAB choose
30-
// which of the two static allocator instances below is currently
31-
// live. The two are unioned by usage, not by struct -- only one is
32-
// initialised at any moment. We pay the (~150 + ~80 B) BSS cost to
33-
// avoid a heap alloc on bench_init.
30+
// MODE_NONE = pre-init / post-teardown. MODE_HEAP / MODE_SLAB / MODE_PAGE
31+
// choose which of the static allocator instances below is currently
32+
// live. They're unioned by usage, not by struct -- only one is
33+
// initialised at any moment.
3434
typedef enum {
3535
MODE_NONE = 0,
3636
MODE_HEAP = 1,
3737
MODE_SLAB = 2,
38+
MODE_PAGE = 3,
3839
} bench_mode;
3940

4041
static HeapAllocator g_heap;
4142
static SlabAllocator g_slab;
43+
static PageAllocator g_page;
4244
static bench_mode g_mode = MODE_NONE;
4345

4446
Zstr bench_backend_name(void) {
@@ -56,6 +58,8 @@ static void tear_current(void) {
5658
HeapAllocatorDeinit(&g_heap);
5759
} else if (g_mode == MODE_SLAB) {
5860
SlabAllocatorDeinit(&g_slab);
61+
} else if (g_mode == MODE_PAGE) {
62+
PageAllocatorDeinit(&g_page);
5963
}
6064
g_mode = MODE_NONE;
6165
}
@@ -69,27 +73,20 @@ void bench_teardown(void) {
6973
tear_current();
7074
}
7175

72-
// Threshold above which the slab's slot-size design (one slab = one OS
73-
// page, slot_size capped at the page) can't fit a single slot. Above
74-
// this, the "correct" backend falls back to HeapAllocator so the bench
75-
// still measures a real allocation rather than NULL returns.
76-
//
77-
// Matches MisraStdC's slab max (currently 4096 B, the smallest OS page
78-
// size we target). If the slab grows multi-page support later, raise
79-
// this threshold to match.
76+
// SlabAllocator caps at MisraStdC's slab max (currently 4096 B, the
77+
// smallest OS page size we target). Above this, the "right tool" is
78+
// PageAllocator -- the request is already page-aligned in size, so
79+
// one mmap per alloc is the honest cost, no rounding waste, no
80+
// general-heap dispatch noise. The previous fallback to Heap measured
81+
// the wrong-tool case, which is exactly what the `misra` (Heap-only)
82+
// column is for.
8083
#define BENCH_SLAB_MAX_SLOT 4096u
8184

8285
void bench_use_fixed_size(size_t slot) {
8386
tear_current();
8487
if (slot > BENCH_SLAB_MAX_SLOT) {
85-
// Slab can't hold a slot this big; fall back to Heap and let
86-
// it route through XL (mmap-per-alloc) the way the upstream
87-
// "wrong tool" backend does. The bench's
88-
// wrong-vs-right-allocator comparison still works for sizes
89-
// within the slab's range; for larger sizes both backends
90-
// converge on the same Heap path.
91-
g_heap = HeapAllocatorInit();
92-
g_mode = MODE_HEAP;
88+
g_page = PageAllocatorInit();
89+
g_mode = MODE_PAGE;
9390
return;
9491
}
9592
g_slab = SlabAllocatorInit((size)slot);
@@ -108,27 +105,26 @@ int bench_can_reset(void) { return 0; }
108105
void bench_reset(void) {}
109106

110107
void *bench_alloc(size_t n) {
111-
// _Generic in AllocatorAlloc routes to slab_allocator_allocate or
112-
// heap_allocator_allocate based on the static type at each call
113-
// site. The g_mode branch is perfectly predicted within a
114-
// benchmark because the mode is set once before the iteration
115-
// loop and never changes inside it.
108+
// _Generic in AllocatorAlloc routes to the typed *_allocator_allocate
109+
// statically. The g_mode branch is perfectly predicted within a
110+
// benchmark -- mode is set once before the iteration loop and
111+
// never changes inside it.
116112
if (g_mode == MODE_SLAB) {
117113
return AllocatorAlloc(&g_slab, (size)n, 0);
118114
}
115+
if (g_mode == MODE_PAGE) {
116+
return AllocatorAlloc(&g_page, (size)n, 0);
117+
}
119118
return AllocatorAlloc(&g_heap, (size)n, 0);
120119
}
121120

122121
void *bench_realloc(void *p, size_t n) {
123-
// SlabAllocator slot size is fixed at init; realloc is not a
124-
// sensible operation in slab mode. The bench's only realloc
125-
// workload (BM_ReallocGrow) runs in MODE_HEAP, so this branch
126-
// should never fire in slab mode. Return NULL (caller treats as
127-
// alloc failure -- the bench then reads the size-class check
128-
// and moves on); we avoid LOG_FATAL here because aborting the
129-
// whole bench process on a routing mismatch would make the
130-
// failure mode hostile to interactive debugging.
131-
if (g_mode == MODE_SLAB) {
122+
// Realloc is sensible only on the general-purpose Heap path.
123+
// BM_ReallocGrow is the sole realloc workload and runs in
124+
// MODE_HEAP, so the slab / page branches should never fire here.
125+
// Return NULL (caller treats as alloc failure) instead of
126+
// LOG_FATAL so a routing mismatch doesn't kill interactive runs.
127+
if (g_mode == MODE_SLAB || g_mode == MODE_PAGE) {
132128
(void)p;
133129
(void)n;
134130
return NULL;
@@ -139,34 +135,30 @@ void *bench_realloc(void *p, size_t n) {
139135
void bench_free(void *p) {
140136
if (g_mode == MODE_SLAB) {
141137
AllocatorFree(&g_slab, p);
138+
} else if (g_mode == MODE_PAGE) {
139+
AllocatorFree(&g_page, p);
142140
} else {
143141
AllocatorFree(&g_heap, p);
144142
}
145143
}
146144

147145
uint64_t bench_live_bytes(void) {
148146
#if FEATURE_ALLOC_STATS
149-
// AllocatorGetStats takes a base `Allocator *`, not a typed
150-
// pointer -- both HeapAllocator and SlabAllocator embed
151-
// `Allocator base` as their first field, so the cast is layout-safe.
152-
AllocatorStats s;
153-
if (g_mode == MODE_SLAB) {
154-
s = AllocatorGetStats(ALLOCATOR_OF(&g_slab));
155-
} else if (g_mode == MODE_HEAP) {
156-
s = AllocatorGetStats(ALLOCATOR_OF(&g_heap));
157-
} else {
158-
return 0;
159-
}
160-
return (uint64_t)s.bytes_in_use;
147+
if (g_mode == MODE_SLAB) return (uint64_t)AllocatorBytesInUse(&g_slab);
148+
if (g_mode == MODE_HEAP) return (uint64_t)AllocatorBytesInUse(&g_heap);
149+
if (g_mode == MODE_PAGE) return (uint64_t)AllocatorBytesInUse(&g_page);
150+
return 0;
161151
#else
162152
return 0;
163153
#endif
164154
}
165155

166156
uint64_t bench_footprint_bytes(void) {
167-
// HeapAllocator and SlabAllocator no longer embed a PageAllocator;
168-
// each talks to the kernel directly. Footprint measurement via the
169-
// PageAllocator entries[] vector is no longer available here.
170-
(void)g_mode;
157+
// Only the page-backed mode can report a meaningful footprint
158+
// today; Heap and Slab manage their own pages with no public
159+
// committed-bytes accessor yet.
160+
if (g_mode == MODE_PAGE) {
161+
return (uint64_t)PageAllocatorFootprintBytes(&g_page);
162+
}
171163
return 0;
172164
}

Benchmark/Source/Allocator_misra_page.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,7 @@ void bench_free(void *p) {
7272

7373
uint64_t bench_live_bytes(void) {
7474
#if FEATURE_ALLOC_STATS
75-
AllocatorStats s = AllocatorGetStats(ALLOCATOR_OF(&g_page));
76-
return (uint64_t)s.bytes_in_use;
75+
return (uint64_t)AllocatorBytesInUse(&g_page);
7776
#else
7877
return 0;
7978
#endif

Benchmark/Source/Bench.cpp

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,27 @@ BENCHMARK(BM_MixedPareto);
120120
// ---------------------------------------------------------------------------
121121
// 5. arena bump + bulk reset
122122
// ---------------------------------------------------------------------------
123-
// Allocate N small objects, then either reset the backing arena (bulk
124-
// O(1) free) or fall back to freeing each pointer individually. Same
125-
// workload on every backend; arena-shaped backends short-circuit to
126-
// reset. Shows the cost differential of "1 reset" vs "N individual
127-
// frees" for the same bump-N workload.
123+
// Allocate N small objects, then reset the backing arena (bulk O(1)
124+
// free). Reports the cost of "1 reset" for a bump-N workload. Only
125+
// runs on backends that actually expose a reset; everyone else gets
126+
// n/a so the column isn't accidentally compared against per-pointer
127+
// free (that comparison lives in BM_BatchAllocFree).
128128
static void BM_ArenaBumpReset(benchmark::State &state) {
129+
if (!bench_can_reset()) {
130+
// This bench is specifically the bulk-reset workload. On
131+
// backends that don't support reset, running per-pointer free
132+
// instead would silently duplicate BM_BatchAllocFree numbers
133+
// and pretend they're an arena-vs-heap comparison. Skip so the
134+
// row shows n/a on those columns honestly.
135+
state.SkipWithMessage("backend has no bulk reset");
136+
return;
137+
}
129138
const size_t n = static_cast<size_t>(state.range(0));
130139
const size_t sz = 32;
131140
std::vector<void *> ptrs(n);
132141
for (auto _ : state) {
133142
for (size_t i = 0; i < n; i++) ptrs[i] = bench_alloc(sz);
134-
if (bench_can_reset()) {
135-
bench_reset();
136-
} else {
137-
for (size_t i = 0; i < n; i++) bench_free(ptrs[i]);
138-
}
143+
bench_reset();
139144
}
140145
state.SetItemsProcessed(int64_t(state.iterations()) * int64_t(n));
141146
}

Benchmark/meson.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ foreach b : bench_libc_backends
7676
endforeach
7777

7878
# Misra backends. Each picks up the in-tree misra_std built above.
79+
# Cross-TU inlining of the trivial `bench_alloc` / `bench_free` /
80+
# `bench_realloc` wrappers into `Bench.cpp`'s hot loop relies on the
81+
# project being configured with `-Db_lto=true`; without that the
82+
# wrapper costs a real function call per iteration.
7983
executable(
8084
'bench-misra',
8185
files('Source/Allocator_misra.c') + bench_cpp_sources,

0 commit comments

Comments
 (0)