You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+28-6Lines changed: 28 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -124,30 +124,52 @@ Traditional stack allocators suffix or prefix each payload with inline metadata
124
124
125
125
## Benchmarks & Performance
126
126
127
-
To verify execution speedand memory overhead, `easy_stack` was benchmarked against popular alternatives:
127
+
To verify execution speed, cache resilience, and scalability under load, `easy_stack` was benchmarked against popular alternatives:
128
128
***wb_alloc (Bundy):** A widely-used, minimalist C arena allocator.
129
129
***Trebi StackAllocator:** A standard C++ LIFO stack allocator (compiled with `-flto` for maximum devirtualization).
130
130
131
131
### Test Environment
132
132
***CPU:** AMD Ryzen 7 4700U (8 Cores / 8 Threads, Zen 2 @ up to 4.1 GHz)
133
133
***Compiler:** GCC 15.2 with `-O3 -flto -DNDEBUG`
134
-
***Scenario:** 2,000,000 iterations per run (executing exactly 72,000,000 allocator operations) of nested allocations (up to depth 15) with randomized sizes (16-160 bytes). Best of 25 runs.
134
+
***Scenario:** 2,000,000 iterations per run (executing up to 480,000,000 allocator operations) of nested allocations with randomized sizes (16-160 bytes) scaled across three distinct stack allocation depths (15, 30, and 100). Best of 25 runs.
135
135
136
-
### 1. Throughput (Speed)
136
+
### 1. Throughput & Cache-Line Scaling (Speed)
137
137
138
-
Even when configured with full runtime safety checks (`ESTACK_POLICY_DEFENSIVE`), `easy_stack` easily outperforms competitors due to its dense L1-cache friendly metadata layout. When compiled in trusted mode (`ESTACK_POLICY_CONTRACT`), it achieves near-hardware limits.
138
+
Rather than benchmarking only a single shallow depth, the suite tests scaling across three critical architectural boundaries. This highlights how cache layout and processor prefetching impact execution speed as stack depth increases.
***EasyStack (Contract):****820 Million ops/sec** (~1.22 ns per allocation/free cycle) — **3.56x faster** than traditional implementations.
145
-
***EasyStack (Defensive):****470 Million ops/sec** (~2.12 ns per cycle) — still **2.06x faster** than competitors, while providing full runtime LIFO safety and validation.
144
+
#### Architectural Insights from the Results:
145
+
146
+
***Perfect L1 Residency (Depth 15):**
147
+
At shallow depths, the EStack header (16 bytes) and active metadata cells (30 bytes for `uint16_t` offsets) fit entirely within a single **64-byte L1 cache line** ($16 + 30 = 46$ bytes). In **Contract** (Trusted) mode, this achieves near-hardware limits of **821 Million ops/sec** (~1.22 ns per allocation/free cycle) due to 100% L1 cache hits.
148
+
149
+
***L1 Cache Line Transitions (Depth 30):**
150
+
As the stack depth crosses the 64-byte boundary ($16 + 60 = 76$ bytes), metadata spans into a second cache line. This transition introduces a minor **6% throughput degradation** (dropping to **766 Million ops/sec**), representing the hardware overhead of fetching the adjacent cache line.
151
+
152
+
***Hardware Prefetcher Synergy (Depth 100):**
153
+
At extreme depths, metadata spans 4 distinct cache lines (216 bytes total). Thanks to the dense, sequential, and contiguous layout of the metadata array, the CPU's **Hardware Prefetcher** instantly recognizes the linear access pattern. It proactively loads upcoming cache lines into L1, maintaining a blistering speed of **682 Million ops/sec** (only a 17% drop from peak L1 residency).
In **Defensive** (Safety) mode, throughput remains completely flat at a rock-solid **~464 Million ops/sec** across all depths (15, 30, and 100).
157
+
158
+
This stability is a textbook demonstration of **CPU-bound execution masking memory latency**. The execution of defensive `if` branches and boundary checks occupies the pipeline with ALU instructions. During this instruction execution window, the CPU's prefetcher asynchronously pulls next-cache-line memory requests in the background. By the time the safety checks are complete, the memory is already waiting in L1, resulting in a **zero-cycle memory stall** regardless of stack depth.
159
+
160
+
#### Comparison to Competitors:
161
+
162
+
Traditional inline-header allocators degrade more severely or remain slow under depth scaling.
163
+
***Trebi (C++)** drops from **199 Million ops/sec** down to **185 Million** at depth 100. This is because inline metadata scatters memory records across the buffer ($100 \times 16 \text{ bytes} = 1600 \text{ bytes}$ of headers mixed with payload). This layout thrashes L1 cache lines with hard-to-predict address jumps, causing frequent processor stalls.
164
+
* Even with full safety checks active, **EasyStack (Defensive)** remains **87% faster than wb_alloc** and **150% faster than Trebi** at depth 100, while **EasyStack (Contract)** outperforms competitors by up to **268%**.
165
+
146
166
147
167
### 2. Memory Efficiency (Payload vs. Overhead)
148
168
149
169
Traditional stack allocators prefix each block with a fixed 16-byte inline header (on 64-bit systems). On small allocations (common for temporary stacks), this inline overhead consumes up to **50%+ of your buffer**.
170
+
150
171
`easy_stack` uses dynamically scaled metadata (only 2 bytes per allocation for buffers < 64KB). Since metadata is segregated from aligned payloads, **zero bytes are wasted on alignment padding in the control zone**.
172
+
151
173
Below is a comparison of usable payload space in a **10 KB buffer** across different allocation sizes:
0 commit comments