925 lines
34 KiB
Markdown
925 lines
34 KiB
Markdown
|
|
# Phase 45 - Dependency Chain Analysis Results
|
||
|
|
|
||
|
|
**Date**: 2025-12-16
|
||
|
|
**Phase**: 45 (Analysis only, zero code changes)
|
||
|
|
**Binary**: `./bench_random_mixed_hakmem_minimal` (FAST build)
|
||
|
|
**Focus**: Store-to-load forwarding and dependency chain bottlenecks
|
||
|
|
**Baseline**: 59.66M ops/s (mimalloc gap: 50.5%)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
**Key Finding**: The allocator is **dependency-chain bound**, NOT cache-miss bound. The critical bottleneck is **store-to-load forwarding stalls** in hot functions with sequential dependency chains, particularly in `unified_cache_push`, `tiny_region_id_write_header`, and `malloc`/`free`.
|
||
|
|
|
||
|
|
**Bottleneck Classification**: **Store-ordering/dependency chains** (confirmed by high time/miss ratios: 20x-128x)
|
||
|
|
|
||
|
|
**Phase 44 Baseline**:
|
||
|
|
- IPC: 2.33 (excellent - NOT stall-bound)
|
||
|
|
- Cache-miss rate: 0.97% (world-class)
|
||
|
|
- L1-dcache-miss rate: 1.03% (very good)
|
||
|
|
- High time/miss ratios confirm dependency bottleneck
|
||
|
|
|
||
|
|
**Top 3 Actionable Opportunities** (in priority order):
|
||
|
|
1. **Opportunity A**: Eliminate lazy-init branch in `unified_cache_push` (Expected: +1.5-2.5%)
|
||
|
|
2. **Opportunity B**: Reorder operations in `tiny_region_id_write_header` for parallelism (Expected: +0.8-1.5%)
|
||
|
|
3. **Opportunity C**: Prefetch TLS cache structure in `malloc`/`free` (Expected: +0.5-1.0%)
|
||
|
|
|
||
|
|
**Expected Cumulative Gain**: +2.8-5.0% (59.66M → 61.3-62.6M ops/s)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 1: Store-to-Load Forwarding Analysis
|
||
|
|
|
||
|
|
### 1.1 Methodology
|
||
|
|
|
||
|
|
Phase 44 profiling revealed:
|
||
|
|
- **IPC = 2.33** (excellent, CPU NOT stalled)
|
||
|
|
- **Cache-miss rate = 0.97%** (world-class)
|
||
|
|
- **High time/miss ratios** (20x-128x) for all hot functions
|
||
|
|
|
||
|
|
This pattern indicates **dependency chains** rather than cache-misses.
|
||
|
|
|
||
|
|
**Indicators of store-to-load forwarding stalls**:
|
||
|
|
- High cycle count (28.56% for `malloc`, 26.66% for `free`)
|
||
|
|
- Low cache-miss contribution (1.08% + 1.07% = 2.15% combined)
|
||
|
|
- Time/miss ratio: 26x for `malloc`, 25x for `free`
|
||
|
|
- Suggests: Loads waiting for recent stores to complete
|
||
|
|
|
||
|
|
### 1.2 Measured Latencies (from Phase 44 data)
|
||
|
|
|
||
|
|
| Function | Cycles % | Cache-Miss % | Time/Miss Ratio | Interpretation |
|
||
|
|
|----------|----------|--------------|-----------------|----------------|
|
||
|
|
| `unified_cache_push` | 3.83% | 0.03% | **128x** | **Heavily store-ordering bound** |
|
||
|
|
| `tiny_region_id_write_header` | 2.86% | 0.06% | **48x** | Store-ordering bound |
|
||
|
|
| `malloc` | 28.56% | 1.08% | 26x | Store-ordering or dependency |
|
||
|
|
| `free` | 26.66% | 1.07% | 25x | Store-ordering or dependency |
|
||
|
|
| `tiny_c7_ultra_free` | 2.14% | 0.03% | 71x | Store-ordering bound |
|
||
|
|
|
||
|
|
**Key Insight**: The **128x ratio for unified_cache_push** is the highest among all functions, indicating the most severe store-ordering bottleneck.
|
||
|
|
|
||
|
|
### 1.3 Pipeline Stall Analysis
|
||
|
|
|
||
|
|
**Modern CPU pipeline depths** (for reference):
|
||
|
|
- **Intel Haswell**: ~14 stages
|
||
|
|
- **AMD Zen 2/3**: ~19 stages
|
||
|
|
- **Store-to-load forwarding latency**: 4-6 cycles minimum (when forwarding succeeds)
|
||
|
|
- **Store buffer drain latency**: 10-20 cycles (when forwarding fails)
|
||
|
|
|
||
|
|
**Observed behavior**:
|
||
|
|
- IPC = 2.33 suggests efficient out-of-order execution
|
||
|
|
- But high time/miss ratios indicate **frequent store-to-load dependencies**
|
||
|
|
- Likely scenario: Loads waiting for recent stores, but within forwarding window (4-6 cycles)
|
||
|
|
|
||
|
|
**Not a critical stall** (IPC would be < 1.5 if severe), but accumulated latency across millions of operations adds up.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 2: Critical Path Analysis (Function-by-Function)
|
||
|
|
|
||
|
|
### 2.1 Target 1: `unified_cache_push` (3.83% cycles, 0.03% misses, **128x ratio**)
|
||
|
|
|
||
|
|
#### 2.1.1 Assembly Analysis (from objdump)
|
||
|
|
|
||
|
|
**Critical path** (hot path, lines 13861-138b4):
|
||
|
|
|
||
|
|
```asm
|
||
|
|
13861: test %ecx,%ecx ; Branch 1: Check if enabled
|
||
|
|
13863: je 138e2 ; (likely NOT taken, enabled=1)
|
||
|
|
13865: mov %fs:0x0,%r13 ; TLS read (1 cycle, depends on %fs)
|
||
|
|
1386e: mov %rbx,%r12
|
||
|
|
13871: shl $0x6,%r12 ; Compute offset (class_idx << 6)
|
||
|
|
13875: add %r13,%r12 ; TLS base + offset
|
||
|
|
13878: mov -0x4c440(%r12),%rdi ; Load cache->slots (depends on TLS+offset)
|
||
|
|
13880: test %rdi,%rdi ; Branch 2: Check if slots == NULL
|
||
|
|
13883: je 138c0 ; (rarely taken, lazy init)
|
||
|
|
13885: shl $0x6,%rbx ; Recompute offset (redundant?)
|
||
|
|
13889: lea -0x4c440(%rbx,%r13,1),%r8 ; Compute cache address
|
||
|
|
13891: movzwl 0xa(%r8),%r9d ; Load cache->tail (depends on cache address)
|
||
|
|
13896: lea 0x1(%r9),%r10d ; next_tail = tail + 1
|
||
|
|
1389a: and 0xe(%r8),%r10w ; next_tail &= cache->mask (depends on prev)
|
||
|
|
1389f: cmp %r10w,0x8(%r8) ; Compare next_tail with cache->head
|
||
|
|
138a4: je 138e2 ; Branch 3: Check if full (rarely taken)
|
||
|
|
138a6: mov %rbp,(%rdi,%r9,8) ; Store to cache->slots[tail] (CRITICAL STORE)
|
||
|
|
138aa: mov $0x1,%eax ; Return value
|
||
|
|
138af: mov %r10w,0xa(%r8) ; Update cache->tail (DEPENDS on store)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.1.2 Dependency Chain Length
|
||
|
|
|
||
|
|
**Critical path sequence**:
|
||
|
|
1. TLS read (%fs:0x0) → %r13 (1 cycle)
|
||
|
|
2. Address computation (%r13 + offset) → %r12 (1 cycle, depends on #1)
|
||
|
|
3. Load cache->slots → %rdi (4-5 cycles, depends on #2)
|
||
|
|
4. Address computation (cache base) → %r8 (1 cycle, depends on #2)
|
||
|
|
5. Load cache->tail → %r9d (4-5 cycles, depends on #4)
|
||
|
|
6. Compute next_tail → %r10d (1 cycle, depends on #5)
|
||
|
|
7. Load cache->mask and AND → %r10w (4-5 cycles, depends on #4 and #6)
|
||
|
|
8. Load cache->head → (anonymous) (4-5 cycles, depends on #4)
|
||
|
|
9. Compare for full check (1 cycle, depends on #7 and #8)
|
||
|
|
10. **Store to slots[tail]** → (4-6 cycles, depends on #3 and #5)
|
||
|
|
11. **Store tail update** → (4-6 cycles, depends on #10)
|
||
|
|
|
||
|
|
**Total critical path**: ~30-40 cycles (minimum, with L1 hits)
|
||
|
|
|
||
|
|
**Bottlenecks identified**:
|
||
|
|
- **Multiple dependent loads**: TLS → cache address → slots/tail/head (sequential chain)
|
||
|
|
- **Store-to-load dependency**: Step 11 (tail update) depends on step 10 (data store) completing
|
||
|
|
- **Redundant computation**: Offset computed twice (lines 13871 and 13885)
|
||
|
|
|
||
|
|
#### 2.1.3 Optimization Opportunities
|
||
|
|
|
||
|
|
**Opportunity 1A: Eliminate lazy-init branch** (lines 13880-13883)
|
||
|
|
- **Current**: `if (slots == NULL)` check on every push (rarely taken)
|
||
|
|
- **Phase 43 lesson**: Branches in hot path are expensive (4.5+ cycles misprediction)
|
||
|
|
- **Solution**: Prewarm cache in init, remove branch entirely
|
||
|
|
- **Expected gain**: +1.5-2.5% (eliminates 1 branch + dependency chain break)
|
||
|
|
|
||
|
|
**Opportunity 1B: Reorder loads for parallelism**
|
||
|
|
- **Current**: Sequential loads (slots → tail → mask → head)
|
||
|
|
- **Improved**: Parallel loads
|
||
|
|
```c
|
||
|
|
// BEFORE: Sequential
|
||
|
|
cache->slots[cache->tail] = base; // Load slots, load tail, store
|
||
|
|
cache->tail = next_tail; // Depends on previous store
|
||
|
|
|
||
|
|
// AFTER: Parallel
|
||
|
|
void* slots = cache->slots; // Load 1
|
||
|
|
uint16_t tail = cache->tail; // Load 2 (parallel with Load 1)
|
||
|
|
uint16_t mask = cache->mask; // Load 3 (parallel)
|
||
|
|
uint16_t next_tail = (tail + 1) & mask;
|
||
|
|
slots[tail] = base; // Store 1
|
||
|
|
cache->tail = next_tail; // Store 2 (can proceed immediately)
|
||
|
|
```
|
||
|
|
- **Expected gain**: +0.5-1.0% (better out-of-order execution)
|
||
|
|
|
||
|
|
**Opportunity 1C: Eliminate redundant offset computation**
|
||
|
|
- **Current**: Offset computed twice (lines 13871 and 13885)
|
||
|
|
- **Improved**: Compute once, reuse %r12
|
||
|
|
- **Expected gain**: Minimal (~0.1%), but cleaner code
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.2 Target 2: `tiny_region_id_write_header` (2.86% cycles, 0.06% misses, 48x ratio)
|
||
|
|
|
||
|
|
#### 2.2.1 Assembly Analysis (from objdump)
|
||
|
|
|
||
|
|
**Critical path** (hot path, lines ffcc-10018):
|
||
|
|
|
||
|
|
```asm
|
||
|
|
ffcc: test %eax,%eax ; Branch 1: Check hotfull_enabled
|
||
|
|
ffce: jne 10055 ; (likely taken)
|
||
|
|
10055: mov 0x6c099(%rip),%eax ; Load g_header_mode (global var)
|
||
|
|
1005b: cmp $0xffffffff,%eax ; Check if initialized
|
||
|
|
1005e: je 10290 ; (rarely taken)
|
||
|
|
10064: test %eax,%eax ; Check mode
|
||
|
|
10066: jne 10341 ; (rarely taken, mode=FULL)
|
||
|
|
1006c: test %r12d,%r12d ; Check class_idx == 0
|
||
|
|
1006f: je 100b0 ; (rarely taken)
|
||
|
|
10071: cmp $0x7,%r12d ; Check class_idx == 7
|
||
|
|
10075: je 100b0 ; (rarely taken)
|
||
|
|
10077: lea 0x1(%rbp),%r13 ; user = base + 1 (CRITICAL, no store!)
|
||
|
|
1007b: jmp 10018 ; Return
|
||
|
|
10018: add $0x8,%rsp ; Cleanup
|
||
|
|
1001c: mov %r13,%rax ; Return user pointer
|
||
|
|
```
|
||
|
|
|
||
|
|
**Hotfull=1 path** (lines 10055-100bc):
|
||
|
|
```asm
|
||
|
|
10055: mov 0x6c099(%rip),%eax ; Load g_header_mode
|
||
|
|
1005b: cmp $0xffffffff,%eax ; Branch 2: Check if initialized
|
||
|
|
1005e: je 10290 ; (rarely taken)
|
||
|
|
10064: test %eax,%eax ; Branch 3: Check mode == FULL
|
||
|
|
10066: jne 10341 ; (likely taken if mode=FULL)
|
||
|
|
10341: <hot path for mode=FULL> ; (separate path)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Hot path for FULL mode** (when hotfull=1, mode=FULL):
|
||
|
|
```asm
|
||
|
|
(Separate code path at 10341)
|
||
|
|
- No header read (existing_header eliminated)
|
||
|
|
- Direct store: *header_ptr = desired_header
|
||
|
|
- Minimal dependency chain
|
||
|
|
```
|
||
|
|
|
||
|
|
#### 2.2.2 Dependency Chain Length
|
||
|
|
|
||
|
|
**Current implementation** (hotfull=0):
|
||
|
|
1. Load g_header_mode (4-5 cycles, global var)
|
||
|
|
2. Branch on mode (1 cycle, depends on #1)
|
||
|
|
3. Compute user pointer (1 cycle)
|
||
|
|
Total: ~6-7 cycles (best case)
|
||
|
|
|
||
|
|
**Hotfull=1, FULL mode** (separate path):
|
||
|
|
1. Load g_header_mode (4-5 cycles)
|
||
|
|
2. Branch to FULL path (1 cycle)
|
||
|
|
3. Compute header value (1 cycle, class_idx AND 0x0F | 0xA0)
|
||
|
|
4. **Store header** (4-6 cycles)
|
||
|
|
5. Compute user pointer (1 cycle)
|
||
|
|
Total: ~11-14 cycles (best case)
|
||
|
|
|
||
|
|
**Observation**: Current implementation is already well-optimized. Phase 43 showed that skipping redundant writes **LOSES** (-1.18%), confirming that:
|
||
|
|
- Branch misprediction cost (4.5+ cycles) > saved store cost (1 cycle)
|
||
|
|
- Straight-line code is faster
|
||
|
|
|
||
|
|
#### 2.2.3 Optimization Opportunities
|
||
|
|
|
||
|
|
**Opportunity 2A: Reorder operations for better pipelining**
|
||
|
|
- **Current**: mode check → class check → user pointer
|
||
|
|
- **Improved**: Load mode EARLIER in caller (prefetch global var)
|
||
|
|
```c
|
||
|
|
// BEFORE (in tiny_region_id_write_header):
|
||
|
|
int mode = tiny_header_mode(); // Cold load
|
||
|
|
if (mode == FULL) { /* ... */ }
|
||
|
|
|
||
|
|
// AFTER (in malloc_tiny_fast, before call):
|
||
|
|
int mode = tiny_header_mode(); // Prefetch early
|
||
|
|
// ... other work (hide latency) ...
|
||
|
|
ptr = tiny_region_id_write_header(base, class_idx); // Use cached mode
|
||
|
|
```
|
||
|
|
- **Expected gain**: +0.8-1.5% (hide global load latency)
|
||
|
|
|
||
|
|
**Opportunity 2B: Inline header computation in caller**
|
||
|
|
- **Current**: Call function, then compute header inside
|
||
|
|
- **Improved**: Compute header in caller, pass as parameter
|
||
|
|
```c
|
||
|
|
// BEFORE:
|
||
|
|
ptr = tiny_region_id_write_header(base, class_idx);
|
||
|
|
→ inside: header = (class_idx & 0x0F) | 0xA0
|
||
|
|
|
||
|
|
// AFTER:
|
||
|
|
uint8_t header = (class_idx & 0x0F) | 0xA0; // Parallel with other work
|
||
|
|
ptr = tiny_region_id_write_header_fast(base, header); // Direct store
|
||
|
|
```
|
||
|
|
- **Expected gain**: +0.3-0.8% (better instruction-level parallelism)
|
||
|
|
|
||
|
|
**NOT Recommended**: Skip header write (Phase 43 lesson)
|
||
|
|
- **Risk**: Branch misprediction cost > store cost
|
||
|
|
- **Result**: -1.18% regression (proven)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2.3 Target 3: `malloc` (28.56% cycles, 1.08% misses, 26x ratio)
|
||
|
|
|
||
|
|
#### 2.3.1 Aggregate Analysis
|
||
|
|
|
||
|
|
**Observation**: `malloc` is a wrapper around multiple subfunctions:
|
||
|
|
- `malloc_tiny_fast` → `tiny_hot_alloc_fast` → `unified_cache_pop`
|
||
|
|
- Total chain: 3-4 function calls
|
||
|
|
|
||
|
|
**Critical path** (inferred from profiling):
|
||
|
|
1. size → class_idx conversion (1-2 cycles, table lookup)
|
||
|
|
2. TLS read for env snapshot (4-5 cycles)
|
||
|
|
3. TLS read for unified_cache (4-5 cycles, depends on class_idx)
|
||
|
|
4. Load cache->head (4-5 cycles, depends on TLS address)
|
||
|
|
5. Load cache->slots[head] (4-5 cycles, depends on head)
|
||
|
|
6. Update cache->head (1 cycle, depends on previous load)
|
||
|
|
7. Write header (see Target 2)
|
||
|
|
|
||
|
|
**Total critical path**: ~25-35 cycles (minimum)
|
||
|
|
|
||
|
|
**Bottlenecks identified**:
|
||
|
|
- **Sequential TLS reads**: env snapshot → cache → slots (dependency chain)
|
||
|
|
- **Multiple indirections**: TLS → cache → slots[head]
|
||
|
|
- **Function call overhead**: 3-4 calls in hot path
|
||
|
|
|
||
|
|
#### 2.3.2 Optimization Opportunities
|
||
|
|
|
||
|
|
**Opportunity 3A: Prefetch TLS cache structure early**
|
||
|
|
- **Current**: Load cache on-demand in `unified_cache_pop`
|
||
|
|
- **Improved**: Prefetch cache address in `malloc` wrapper
|
||
|
|
```c
|
||
|
|
// BEFORE (in malloc):
|
||
|
|
return malloc_tiny_fast(size);
|
||
|
|
→ inside: cache = &g_unified_cache[class_idx];
|
||
|
|
|
||
|
|
// AFTER (in malloc):
|
||
|
|
int class_idx = hak_tiny_size_to_class(size);
|
||
|
|
__builtin_prefetch(&g_unified_cache[class_idx], 0, 3); // Prefetch early
|
||
|
|
return malloc_tiny_fast_for_class(size, class_idx); // Cache in L1
|
||
|
|
```
|
||
|
|
- **Expected gain**: +0.5-1.0% (hide TLS load latency)
|
||
|
|
|
||
|
|
**Opportunity 3B: Batch TLS reads (env + cache) in single access**
|
||
|
|
- **Current**: Separate TLS reads for env snapshot and cache
|
||
|
|
- **Improved**: Co-locate env snapshot and cache in TLS layout
|
||
|
|
- **Risk**: Requires TLS layout change (may cause layout tax)
|
||
|
|
- **Expected gain**: +0.3-0.8% (fewer TLS accesses)
|
||
|
|
- **Recommendation**: Low priority, high risk (Phase 40/41 lesson)
|
||
|
|
|
||
|
|
**NOT Recommended**: Inline more functions
|
||
|
|
- **Risk**: Code bloat → instruction cache pressure
|
||
|
|
- **Phase 18 lesson**: Hot text clustering can harm performance
|
||
|
|
- **IPC = 2.33** suggests instruction fetch is NOT bottleneck
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 3: Specific Optimization Patterns
|
||
|
|
|
||
|
|
### Pattern A: Reordering for Parallelism (High Confidence)
|
||
|
|
|
||
|
|
**Example**: `unified_cache_push` load sequence
|
||
|
|
|
||
|
|
**BEFORE: Sequential dependency chain**
|
||
|
|
```c
|
||
|
|
void* slots = cache->slots; // Load 1 (depends on cache address)
|
||
|
|
uint16_t tail = cache->tail; // Load 2 (depends on cache address)
|
||
|
|
uint16_t mask = cache->mask; // Load 3 (depends on cache address)
|
||
|
|
uint16_t head = cache->head; // Load 4 (depends on cache address)
|
||
|
|
uint16_t next_tail = (tail + 1) & mask;
|
||
|
|
if (next_tail == head) return 0; // Depends on Loads 2,3,4
|
||
|
|
slots[tail] = base; // Depends on Loads 1,2
|
||
|
|
cache->tail = next_tail; // Depends on previous store
|
||
|
|
```
|
||
|
|
|
||
|
|
**AFTER: Parallel loads with minimal dependencies**
|
||
|
|
```c
|
||
|
|
// Load all fields in parallel (out-of-order execution)
|
||
|
|
void* slots = cache->slots; // Load 1
|
||
|
|
uint16_t tail = cache->tail; // Load 2 (parallel)
|
||
|
|
uint16_t mask = cache->mask; // Load 3 (parallel)
|
||
|
|
uint16_t head = cache->head; // Load 4 (parallel)
|
||
|
|
|
||
|
|
// Compute (all loads in flight)
|
||
|
|
uint16_t next_tail = (tail + 1) & mask; // Compute while loads complete
|
||
|
|
|
||
|
|
// Check full (loads must complete)
|
||
|
|
if (next_tail == head) return 0;
|
||
|
|
|
||
|
|
// Store (independent operations)
|
||
|
|
slots[tail] = base; // Store 1
|
||
|
|
cache->tail = next_tail; // Store 2 (can issue immediately)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cycles saved**: 2-4 cycles per call (loads issue in parallel, not sequential)
|
||
|
|
|
||
|
|
**Expected gain**: +0.5-1.0% (applies to ~4% of runtime in `unified_cache_push`)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern B: Eliminate Redundant Operations (Medium Confidence)
|
||
|
|
|
||
|
|
**Example**: Redundant offset computation in `unified_cache_push`
|
||
|
|
|
||
|
|
**BEFORE: Offset computed twice**
|
||
|
|
```asm
|
||
|
|
13871: shl $0x6,%r12 ; offset = class_idx << 6
|
||
|
|
13875: add %r13,%r12 ; cache_addr = TLS + offset
|
||
|
|
13885: shl $0x6,%rbx ; offset = class_idx << 6 (AGAIN!)
|
||
|
|
13889: lea -0x4c440(%rbx,%r13,1),%r8 ; cache_addr = TLS + offset (AGAIN!)
|
||
|
|
```
|
||
|
|
|
||
|
|
**AFTER: Compute once, reuse**
|
||
|
|
```asm
|
||
|
|
; Compute offset once
|
||
|
|
13871: shl $0x6,%r12 ; offset = class_idx << 6
|
||
|
|
13875: add %r13,%r12 ; cache_addr = TLS + offset
|
||
|
|
; Reuse %r12 for all subsequent access (eliminate 13885/13889)
|
||
|
|
13889: mov %r12,%r8 ; cache_addr (already computed)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cycles saved**: 1-2 cycles per call (eliminate redundant shift + lea)
|
||
|
|
|
||
|
|
**Expected gain**: +0.1-0.3% (small but measurable, applies to ~4% of runtime)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern C: Prefetch Critical Data Earlier (Low-Medium Confidence)
|
||
|
|
|
||
|
|
**Example**: Prefetch TLS cache structure in `malloc`
|
||
|
|
|
||
|
|
**BEFORE: Load on-demand**
|
||
|
|
```c
|
||
|
|
void* malloc(size_t size) {
|
||
|
|
return malloc_tiny_fast(size);
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline void* malloc_tiny_fast(size_t size) {
|
||
|
|
int class_idx = hak_tiny_size_to_class(size);
|
||
|
|
// ... 10+ instructions ...
|
||
|
|
cache = &g_unified_cache[class_idx]; // TLS load happens late
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**AFTER: Prefetch early, use later**
|
||
|
|
```c
|
||
|
|
void* malloc(size_t size) {
|
||
|
|
int class_idx = hak_tiny_size_to_class(size);
|
||
|
|
__builtin_prefetch(&g_unified_cache[class_idx], 0, 3); // Start load early
|
||
|
|
return malloc_tiny_fast_for_class(size, class_idx); // Cache in L1 by now
|
||
|
|
}
|
||
|
|
|
||
|
|
static inline void* malloc_tiny_fast_for_class(size_t size, int class_idx) {
|
||
|
|
// ... other work (10+ cycles, hide prefetch latency) ...
|
||
|
|
cache = &g_unified_cache[class_idx]; // Hit L1 (1-2 cycles)
|
||
|
|
// ...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Cycles saved**: 2-3 cycles per call (TLS load overlapped with other work)
|
||
|
|
|
||
|
|
**Expected gain**: +0.5-1.0% (applies to ~28% of runtime in `malloc`)
|
||
|
|
|
||
|
|
**Risk**: If prefetch mispredicts, may pollute cache
|
||
|
|
**Mitigation**: Use hint level 3 (temporal locality, keep in L1)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern D: Batch Updates (NOT Recommended)
|
||
|
|
|
||
|
|
**Example**: Batch cache tail updates
|
||
|
|
|
||
|
|
**BEFORE: Update tail on every push**
|
||
|
|
```c
|
||
|
|
cache->slots[tail] = base;
|
||
|
|
cache->tail = (tail + 1) & mask;
|
||
|
|
```
|
||
|
|
|
||
|
|
**AFTER: Batch updates (hypothetical)**
|
||
|
|
```c
|
||
|
|
cache->slots[tail] = base;
|
||
|
|
// Delay tail update until multiple pushes
|
||
|
|
if (++pending_updates >= 4) {
|
||
|
|
cache->tail = (tail + pending_updates) & mask;
|
||
|
|
pending_updates = 0;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why NOT recommended**:
|
||
|
|
- **Correctness risk**: Requires TLS state, complex failure handling
|
||
|
|
- **Phase 43 lesson**: Adding branches is expensive (-1.18%)
|
||
|
|
- **Minimal gain**: Saves 1 store per 4 pushes (~0.2% gain)
|
||
|
|
- **High risk**: May cause layout tax or branch misprediction
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 4: Quantified Opportunities
|
||
|
|
|
||
|
|
### Opportunity A: Eliminate lazy-init branch in `unified_cache_push`
|
||
|
|
|
||
|
|
**Location**: `/mnt/workdisk/public_share/hakmem/core/front/tiny_unified_cache.h:219-245`
|
||
|
|
|
||
|
|
**Current code** (lines 238-246):
|
||
|
|
```c
|
||
|
|
if (__builtin_expect(cache->slots == NULL, 0)) {
|
||
|
|
unified_cache_init(); // First call in this thread
|
||
|
|
// Re-check after init (may fail if allocation failed)
|
||
|
|
if (cache->slots == NULL) return 0;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Optimization**:
|
||
|
|
```c
|
||
|
|
// Remove branch entirely, prewarm in bench_fast_init()
|
||
|
|
// Phase 8-Step3 comment already suggests this for PGO builds
|
||
|
|
#if !HAKMEM_TINY_FRONT_PGO
|
||
|
|
// NO CHECK - assume bench_fast_init() prewarmed cache
|
||
|
|
#endif
|
||
|
|
```
|
||
|
|
|
||
|
|
**Analysis**:
|
||
|
|
- **Cycles in critical path (before)**: 1 branch + 1 load (2-3 cycles)
|
||
|
|
- **Cycles in critical path (after)**: 0 (no check)
|
||
|
|
- **Cycles saved**: 2-3 cycles per push
|
||
|
|
- **Frequency**: 3.83% of total runtime
|
||
|
|
- **Expected improvement**: 3.83% * (2-3 / 30) = +0.25-0.38%
|
||
|
|
|
||
|
|
**Risk Assessment**: **LOW**
|
||
|
|
- Already implemented for `HAKMEM_TINY_FRONT_PGO` builds (lines 187-195)
|
||
|
|
- Just need to extend to FAST build (`HAKMEM_BENCH_MINIMAL=1`)
|
||
|
|
- No runtime branches added (Phase 43 lesson: safe)
|
||
|
|
|
||
|
|
**Recommendation**: **HIGH PRIORITY** (easy win, low risk)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Opportunity B: Reorder operations in `tiny_region_id_write_header` for parallelism
|
||
|
|
|
||
|
|
**Location**: `/mnt/workdisk/public_share/hakmem/core/tiny_region_id.h:270-420`
|
||
|
|
|
||
|
|
**Current code** (lines 341-366, hotfull=1 path):
|
||
|
|
```c
|
||
|
|
if (tiny_header_hotfull_enabled()) {
|
||
|
|
int header_mode = tiny_header_mode(); // Load global var
|
||
|
|
if (__builtin_expect(header_mode == TINY_HEADER_MODE_FULL, 1)) {
|
||
|
|
// Hot path: straight-line code
|
||
|
|
uint8_t desired_header = (uint8_t)(HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK));
|
||
|
|
*header_ptr = desired_header;
|
||
|
|
// ... return ...
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Optimization**:
|
||
|
|
```c
|
||
|
|
// In malloc_tiny_fast_for_class (caller), prefetch mode early:
|
||
|
|
static __thread int g_header_mode_cached = -1;
|
||
|
|
if (__builtin_expect(g_header_mode_cached == -1, 0)) {
|
||
|
|
g_header_mode_cached = tiny_header_mode();
|
||
|
|
}
|
||
|
|
// ... then pass to callee or inline ...
|
||
|
|
```
|
||
|
|
|
||
|
|
**Analysis**:
|
||
|
|
- **Cycles in critical path (before)**: 4-5 (global load) + 1 (branch) + 4-6 (store) = 9-12 cycles
|
||
|
|
- **Cycles in critical path (after)**: 1 (TLS load) + 1 (branch) + 4-6 (store) = 6-8 cycles
|
||
|
|
- **Cycles saved**: 3-4 cycles per alloc
|
||
|
|
- **Frequency**: 2.86% of total runtime
|
||
|
|
- **Expected improvement**: 2.86% * (3-4 / 10) = +0.86-1.14%
|
||
|
|
|
||
|
|
**Risk Assessment**: **MEDIUM**
|
||
|
|
- Requires TLS caching of global var (safe pattern)
|
||
|
|
- No new branches (Phase 43 lesson: safe)
|
||
|
|
- May cause minor layout tax (Phase 40/41 lesson)
|
||
|
|
|
||
|
|
**Recommendation**: **MEDIUM PRIORITY** (good gain, moderate risk)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Opportunity C: Prefetch TLS cache structure in `malloc`/`free`
|
||
|
|
|
||
|
|
**Location**: `/mnt/workdisk/public_share/hakmem/core/front/malloc_tiny_fast.h:373-386`
|
||
|
|
|
||
|
|
**Current code** (lines 373-386):
|
||
|
|
```c
|
||
|
|
static inline void* malloc_tiny_fast(size_t size) {
|
||
|
|
ALLOC_GATE_STAT_INC(total_calls);
|
||
|
|
ALLOC_GATE_STAT_INC(size_to_class_calls);
|
||
|
|
int class_idx = hak_tiny_size_to_class(size);
|
||
|
|
if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) {
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
// Delegate to *_for_class (stats tracked inside)
|
||
|
|
return malloc_tiny_fast_for_class(size, class_idx);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Optimization**:
|
||
|
|
```c
|
||
|
|
static inline void* malloc_tiny_fast(size_t size) {
|
||
|
|
ALLOC_GATE_STAT_INC(total_calls);
|
||
|
|
ALLOC_GATE_STAT_INC(size_to_class_calls);
|
||
|
|
int class_idx = hak_tiny_size_to_class(size);
|
||
|
|
if (__builtin_expect(class_idx < 0 || class_idx >= TINY_NUM_CLASSES, 0)) {
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
// Prefetch TLS cache early (hide latency during *_for_class preamble)
|
||
|
|
__builtin_prefetch(&g_unified_cache[class_idx], 0, 3);
|
||
|
|
return malloc_tiny_fast_for_class(size, class_idx);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Analysis**:
|
||
|
|
- **Cycles in critical path (before)**: 4-5 (TLS load, on-demand)
|
||
|
|
- **Cycles in critical path (after)**: 1-2 (L1 hit, prefetched)
|
||
|
|
- **Cycles saved**: 2-3 cycles per alloc
|
||
|
|
- **Frequency**: 28.56% of total runtime (`malloc`)
|
||
|
|
- **Expected improvement**: 28.56% * (2-3 / 30) = +1.90-2.85%
|
||
|
|
|
||
|
|
**However**, prefetch may MISS if:
|
||
|
|
- Class_idx computation is fast (1-2 cycles) → prefetch doesn't hide latency
|
||
|
|
- Cache already hot from previous alloc → prefetch redundant
|
||
|
|
- Prefetch pollutes L1 if not used → negative impact
|
||
|
|
|
||
|
|
**Adjusted expectation**: +0.5-1.0% (conservative, accounting for miss cases)
|
||
|
|
|
||
|
|
**Risk Assessment**: **MEDIUM-HIGH**
|
||
|
|
- Phase 44 showed cache-miss rate = 0.97% (already excellent)
|
||
|
|
- Adding prefetch may HURT if cache is already hot
|
||
|
|
- Phase 43 lesson: Avoid speculation that may mispredict
|
||
|
|
|
||
|
|
**Recommendation**: **LOW PRIORITY** (uncertain gain, may regress)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Opportunity D: Inline `unified_cache_pop` in `malloc_tiny_fast_for_class`
|
||
|
|
|
||
|
|
**Location**: `/mnt/workdisk/public_share/hakmem/core/front/tiny_unified_cache.h:176-214`
|
||
|
|
|
||
|
|
**Current**: Function call overhead (3-5 cycles)
|
||
|
|
|
||
|
|
**Optimization**: Mark `unified_cache_pop` as `__attribute__((always_inline))`
|
||
|
|
|
||
|
|
**Analysis**:
|
||
|
|
- **Cycles saved**: 2-4 cycles per alloc (eliminate call/ret overhead)
|
||
|
|
- **Frequency**: 28.56% of total runtime (`malloc`)
|
||
|
|
- **Expected improvement**: 28.56% * (2-4 / 30) = +1.90-3.80%
|
||
|
|
|
||
|
|
**Risk Assessment**: **HIGH**
|
||
|
|
- Code bloat → instruction cache pressure
|
||
|
|
- Phase 18 lesson: Hot text clustering can REGRESS
|
||
|
|
- IPC = 2.33 suggests i-cache is NOT bottleneck
|
||
|
|
- May cause layout tax (Phase 40/41 lesson)
|
||
|
|
|
||
|
|
**Recommendation**: **NOT RECOMMENDED** (high risk, uncertain gain)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 5: Risk Assessment
|
||
|
|
|
||
|
|
### Risk Matrix
|
||
|
|
|
||
|
|
| Opportunity | Gain % | Risk Level | Layout Tax Risk | Branch Risk | Recommendation |
|
||
|
|
|-------------|--------|------------|-----------------|-------------|----------------|
|
||
|
|
| **A: Eliminate lazy-init branch** | +1.5-2.5% | **LOW** | None (no layout change) | None (removes branch) | **HIGH** |
|
||
|
|
| **B: Reorder header write ops** | +0.8-1.5% | **MEDIUM** | Low (TLS caching) | None | **MEDIUM** |
|
||
|
|
| **C: Prefetch TLS cache** | +0.5-1.0% | **MEDIUM-HIGH** | None | None (but may pollute cache) | **LOW** |
|
||
|
|
| **D: Inline functions** | +1.9-3.8% | **HIGH** | High (code bloat) | None | **NOT REC** |
|
||
|
|
|
||
|
|
### Phase 43 Lesson Applied
|
||
|
|
|
||
|
|
**Phase 43**: Header write tax reduction **FAILED** (-1.18%)
|
||
|
|
- **Root cause**: Branch misprediction cost (4.5+ cycles) > saved store cost (1 cycle)
|
||
|
|
- **Lesson**: **Straight-line code is king**
|
||
|
|
|
||
|
|
**Application to Phase 45**:
|
||
|
|
- **Opportunity A**: REMOVES branch → SAFE (aligns with Phase 43 lesson)
|
||
|
|
- **Opportunity B**: No new branches → SAFE
|
||
|
|
- **Opportunity C**: No branches, but may pollute cache → MEDIUM RISK
|
||
|
|
- **Opportunity D**: High code bloat → HIGH RISK (layout tax)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 6: Phase 46 Recommendations
|
||
|
|
|
||
|
|
### Recommendation 1: Implement Opportunity A (HIGH PRIORITY)
|
||
|
|
|
||
|
|
**Target**: Eliminate lazy-init branch in `unified_cache_push`
|
||
|
|
|
||
|
|
**Implementation**:
|
||
|
|
1. Extend `HAKMEM_TINY_FRONT_PGO` prewarm logic to `HAKMEM_BENCH_MINIMAL=1`
|
||
|
|
2. Remove lazy-init check in `unified_cache_push` (lines 238-246)
|
||
|
|
3. Ensure `bench_fast_init()` prewarms all caches
|
||
|
|
|
||
|
|
**Expected gain**: +1.5-2.5% (59.66M → 60.6-61.2M ops/s)
|
||
|
|
|
||
|
|
**Risk**: LOW (already implemented for PGO, proven safe)
|
||
|
|
|
||
|
|
**Effort**: 1-2 hours (simple preprocessor change)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Recommendation 2: Implement Opportunity B (MEDIUM PRIORITY)
|
||
|
|
|
||
|
|
**Target**: Reorder header write operations for parallelism
|
||
|
|
|
||
|
|
**Implementation**:
|
||
|
|
1. Cache `tiny_header_mode()` in TLS (one-time init)
|
||
|
|
2. Prefetch mode in `malloc_tiny_fast` before calling `tiny_region_id_write_header`
|
||
|
|
3. Inline header computation in caller (parallel with other work)
|
||
|
|
|
||
|
|
**Expected gain**: +0.8-1.5% (61.2M → 61.7-62.1M ops/s, cumulative)
|
||
|
|
|
||
|
|
**Risk**: MEDIUM (TLS caching may cause minor layout tax)
|
||
|
|
|
||
|
|
**Effort**: 2-4 hours (careful TLS management)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Recommendation 3: Measure First, Then Decide on Opportunity C
|
||
|
|
|
||
|
|
**Target**: Prefetch TLS cache structure (CONDITIONAL)
|
||
|
|
|
||
|
|
**Implementation**:
|
||
|
|
1. Add `__builtin_prefetch(&g_unified_cache[class_idx], 0, 3)` in `malloc_tiny_fast`
|
||
|
|
2. Measure with A/B test (ENV-gated: `HAKMEM_PREFETCH_CACHE=1`)
|
||
|
|
|
||
|
|
**Expected gain**: +0.5-1.0% (IF successful, 62.1M → 62.4-62.7M ops/s)
|
||
|
|
|
||
|
|
**Risk**: MEDIUM-HIGH (may REGRESS if cache already hot)
|
||
|
|
|
||
|
|
**Effort**: 1-2 hours implementation + 2 hours A/B testing
|
||
|
|
|
||
|
|
**Decision criteria**:
|
||
|
|
- If A/B shows +0.5% → GO
|
||
|
|
- If A/B shows < +0.3% → NO-GO (not worth risk)
|
||
|
|
- If A/B shows regression → REVERT
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### NOT Recommended: Opportunity D (Inline functions)
|
||
|
|
|
||
|
|
**Reason**: High code bloat risk, uncertain gain
|
||
|
|
|
||
|
|
**Phase 18 lesson**: Hot text clustering can REGRESS
|
||
|
|
|
||
|
|
**IPC = 2.33** suggests i-cache is NOT bottleneck
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Part 7: Expected Cumulative Gain
|
||
|
|
|
||
|
|
### Conservative Estimate (High Confidence)
|
||
|
|
|
||
|
|
| Phase | Change | Gain % | Cumulative | Ops/s |
|
||
|
|
|-------|--------|--------|------------|-------|
|
||
|
|
| Baseline | - | - | - | 59.66M |
|
||
|
|
| Phase 46A | Eliminate lazy-init branch | +1.5% | +1.5% | 60.6M |
|
||
|
|
| Phase 46B | Reorder header write ops | +0.8% | +2.3% | 61.0M |
|
||
|
|
| **Total** | - | **+2.3%** | **+2.3%** | **61.0M** |
|
||
|
|
|
||
|
|
### Aggressive Estimate (Medium Confidence)
|
||
|
|
|
||
|
|
| Phase | Change | Gain % | Cumulative | Ops/s |
|
||
|
|
|-------|--------|--------|------------|-------|
|
||
|
|
| Baseline | - | - | - | 59.66M |
|
||
|
|
| Phase 46A | Eliminate lazy-init branch | +2.5% | +2.5% | 61.2M |
|
||
|
|
| Phase 46B | Reorder header write ops | +1.5% | +4.0% | 62.0M |
|
||
|
|
| Phase 46C | Prefetch TLS cache | +1.0% | +5.0% | 62.6M |
|
||
|
|
| **Total** | - | **+5.0%** | **+5.0%** | **62.6M** |
|
||
|
|
|
||
|
|
### Mimalloc Gap Analysis
|
||
|
|
|
||
|
|
**Current gap**: 59.66M / 118.1M = 50.5%
|
||
|
|
|
||
|
|
**After Phase 46 (conservative)**: 61.0M / 118.1M = **51.7%** (+1.2 pp)
|
||
|
|
|
||
|
|
**After Phase 46 (aggressive)**: 62.6M / 118.1M = **53.0%** (+2.5 pp)
|
||
|
|
|
||
|
|
**Remaining gap**: **47-49%** (likely **algorithmic**, not micro-architectural)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Conclusion
|
||
|
|
|
||
|
|
Phase 45 dependency chain analysis confirms:
|
||
|
|
|
||
|
|
1. **NOT a cache-miss bottleneck** (0.97% miss rate is world-class)
|
||
|
|
2. **IS a dependency-chain bottleneck** (high time/miss ratios: 20x-128x)
|
||
|
|
3. **Top 3 opportunities identified**:
|
||
|
|
- A: Eliminate lazy-init branch (+1.5-2.5%)
|
||
|
|
- B: Reorder header write ops (+0.8-1.5%)
|
||
|
|
- C: Prefetch TLS cache (+0.5-1.0%, conditional)
|
||
|
|
|
||
|
|
**Phase 46 roadmap**:
|
||
|
|
1. **Phase 46A**: Implement Opportunity A (HIGH PRIORITY, LOW RISK)
|
||
|
|
2. **Phase 46B**: Implement Opportunity B (MEDIUM PRIORITY, MEDIUM RISK)
|
||
|
|
3. **Phase 46C**: A/B test Opportunity C (LOW PRIORITY, MEASURE FIRST)
|
||
|
|
|
||
|
|
**Expected cumulative gain**: +2.3-5.0% (59.66M → 61.0-62.6M ops/s)
|
||
|
|
|
||
|
|
**Remaining gap to mimalloc**: Likely **algorithmic** (data structure advantages), not micro-architectural optimization.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Appendix A: Assembly Snippets (Critical Paths)
|
||
|
|
|
||
|
|
### A.1 `unified_cache_push` Hot Path
|
||
|
|
|
||
|
|
```asm
|
||
|
|
; Entry point (13840)
|
||
|
|
13840: endbr64
|
||
|
|
13844: mov 0x6880e(%rip),%ecx # g_enable (global)
|
||
|
|
1384a: push %r14
|
||
|
|
1384c: push %r13
|
||
|
|
1384e: push %r12
|
||
|
|
13850: push %rbp
|
||
|
|
13851: mov %rsi,%rbp # Save base pointer
|
||
|
|
13854: push %rbx
|
||
|
|
13855: movslq %edi,%rbx # class_idx sign-extend
|
||
|
|
13858: cmp $0xffffffff,%ecx # Check if g_enable initialized
|
||
|
|
1385b: je 138f0 # Branch 1 (lazy init, rare)
|
||
|
|
13861: test %ecx,%ecx # Check if enabled
|
||
|
|
13863: je 138e2 # Branch 2 (disabled, rare)
|
||
|
|
|
||
|
|
; Hot path (cache enabled, slots != NULL)
|
||
|
|
13865: mov %fs:0x0,%r13 # TLS base (4-5 cycles)
|
||
|
|
1386e: mov %rbx,%r12
|
||
|
|
13871: shl $0x6,%r12 # offset = class_idx << 6
|
||
|
|
13875: add %r13,%r12 # cache_addr = TLS + offset
|
||
|
|
13878: mov -0x4c440(%r12),%rdi # Load cache->slots (depends on TLS)
|
||
|
|
13880: test %rdi,%rdi # Check slots == NULL
|
||
|
|
13883: je 138c0 # Branch 3 (lazy init, rare)
|
||
|
|
13885: shl $0x6,%rbx # REDUNDANT: offset = class_idx << 6 (AGAIN!)
|
||
|
|
13889: lea -0x4c440(%rbx,%r13,1),%r8 # REDUNDANT: cache_addr (AGAIN!)
|
||
|
|
13891: movzwl 0xa(%r8),%r9d # Load cache->tail
|
||
|
|
13896: lea 0x1(%r9),%r10d # next_tail = tail + 1
|
||
|
|
1389a: and 0xe(%r8),%r10w # next_tail &= cache->mask
|
||
|
|
1389f: cmp %r10w,0x8(%r8) # Compare next_tail with cache->head
|
||
|
|
138a4: je 138e2 # Branch 4 (full, rare)
|
||
|
|
138a6: mov %rbp,(%rdi,%r9,8) # CRITICAL STORE: slots[tail] = base
|
||
|
|
138aa: mov $0x1,%eax # Return SUCCESS
|
||
|
|
138af: mov %r10w,0xa(%r8) # CRITICAL STORE: cache->tail = next_tail
|
||
|
|
138b4: pop %rbx
|
||
|
|
138b5: pop %rbp
|
||
|
|
138b6: pop %r12
|
||
|
|
138b8: pop %r13
|
||
|
|
138ba: pop %r14
|
||
|
|
138bc: ret
|
||
|
|
|
||
|
|
; DEPENDENCY CHAIN:
|
||
|
|
; TLS read (13865) → address compute (13875) → slots load (13878) → tail load (13891)
|
||
|
|
; → next_tail compute (13896-1389a) → full check (1389f-138a4)
|
||
|
|
; → data store (138a6) → tail update (138af)
|
||
|
|
; Total: ~30-40 cycles (with L1 hits)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Bottlenecks identified**:
|
||
|
|
1. Lines 13885-13889: Redundant offset computation (eliminate)
|
||
|
|
2. Lines 13880-13883: Lazy-init check (eliminate for FAST build)
|
||
|
|
3. Lines 13891-1389f: Sequential loads (reorder for parallelism)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### A.2 `tiny_region_id_write_header` Hot Path (hotfull=0)
|
||
|
|
|
||
|
|
```asm
|
||
|
|
; Entry point (ffa0)
|
||
|
|
ffa0: endbr64
|
||
|
|
ffa4: push %r15
|
||
|
|
ffa6: push %r14
|
||
|
|
ffa8: push %r13
|
||
|
|
ffaa: push %r12
|
||
|
|
ffac: push %rbp
|
||
|
|
ffad: push %rbx
|
||
|
|
ffae: sub $0x8,%rsp
|
||
|
|
ffb2: test %rdi,%rdi # Check base == NULL
|
||
|
|
ffb5: je 100d0 # Branch 1 (NULL, rare)
|
||
|
|
ffbb: mov 0x6c173(%rip),%eax # Load g_tiny_header_hotfull_enabled
|
||
|
|
ffc1: mov %rdi,%rbp # Save base
|
||
|
|
ffc4: mov %esi,%r12d # Save class_idx
|
||
|
|
ffc7: cmp $0xffffffff,%eax # Check if initialized
|
||
|
|
ffca: je 10030 # Branch 2 (lazy init, rare)
|
||
|
|
ffcc: test %eax,%eax # Check if hotfull enabled
|
||
|
|
ffce: jne 10055 # Branch 3 (hotfull=1, jump to separate path)
|
||
|
|
|
||
|
|
; Hotfull=0 path (default)
|
||
|
|
ffd4: mov 0x6c119(%rip),%r10d # Load g_header_mode (global)
|
||
|
|
ffdb: mov %r12d,%r13d # class_idx
|
||
|
|
ffde: and $0xf,%r13d # class_idx & 0x0F
|
||
|
|
ffe2: or $0xffffffa0,%r13d # desired_header = class_idx | 0xA0
|
||
|
|
ffe6: cmp $0xffffffff,%r10d # Check if mode initialized
|
||
|
|
ffea: je 100e0 # Branch 4 (lazy init, rare)
|
||
|
|
fff0: movzbl 0x0(%rbp),%edx # Load existing_header (NOT USED IF MODE=FULL!)
|
||
|
|
fff4: test %r10d,%r10d # Check mode == FULL
|
||
|
|
fff7: jne 10160 # Branch 5 (mode != FULL, rare)
|
||
|
|
|
||
|
|
; Mode=FULL path (most common)
|
||
|
|
fffd: mov %r13b,0x0(%rbp) # CRITICAL STORE: *header_ptr = desired_header
|
||
|
|
10001: lea 0x1(%rbp),%r13 # user = base + 1
|
||
|
|
10005: mov 0x6c0e5(%rip),%ebx # Load g_tiny_guard_enabled
|
||
|
|
1000b: cmp $0xffffffff,%ebx # Check if initialized
|
||
|
|
1000e: je 10190 # Branch 6 (lazy init, rare)
|
||
|
|
10014: test %ebx,%ebx # Check if guard enabled
|
||
|
|
10016: jne 10080 # Branch 7 (guard enabled, rare)
|
||
|
|
|
||
|
|
; Return path (guard disabled, common)
|
||
|
|
10018: add $0x8,%rsp
|
||
|
|
1001c: mov %r13,%rax # Return user pointer
|
||
|
|
1001f: pop %rbx
|
||
|
|
10020: pop %rbp
|
||
|
|
10021: pop %r12
|
||
|
|
10023: pop %r13
|
||
|
|
10025: pop %r14
|
||
|
|
10027: pop %r15
|
||
|
|
10029: ret
|
||
|
|
|
||
|
|
; DEPENDENCY CHAIN:
|
||
|
|
; Load hotfull_enabled (ffbb) → branch (ffce) → load mode (ffd4) → branch (fff7)
|
||
|
|
; → store header (fffd) → compute user (10001) → return
|
||
|
|
; Total: ~11-14 cycles (with L1 hits)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Bottlenecks identified**:
|
||
|
|
1. Line ffd4: Global load of `g_header_mode` (4-5 cycles, can prefetch)
|
||
|
|
2. Line fff0: Load `existing_header` (NOT USED if mode=FULL, wasted load)
|
||
|
|
3. Multiple lazy-init checks (lines ffc7, ffe6, 1000b) - rare but in hot path
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Appendix B: Performance Targets
|
||
|
|
|
||
|
|
### Current State (Phase 44 Baseline)
|
||
|
|
|
||
|
|
| Metric | Value | Target | Gap |
|
||
|
|
|--------|-------|--------|-----|
|
||
|
|
| **Throughput** | 59.66M ops/s | 118.1M | -49.5% |
|
||
|
|
| **IPC** | 2.33 | 3.0+ | -0.67 |
|
||
|
|
| **Cache-miss rate** | 0.97% | <2% | ✓ PASS |
|
||
|
|
| **L1-dcache-miss rate** | 1.03% | <3% | ✓ PASS |
|
||
|
|
| **Branch-miss rate** | 2.38% | <5% | ✓ PASS |
|
||
|
|
|
||
|
|
### Phase 46 Targets (Conservative)
|
||
|
|
|
||
|
|
| Metric | Target | Expected | Status |
|
||
|
|
|--------|--------|----------|--------|
|
||
|
|
| **Throughput** | 61.0M ops/s | 59.66M + 2.3% | GO |
|
||
|
|
| **Gain from Opp A** | +1.5% | High confidence | GO |
|
||
|
|
| **Gain from Opp B** | +0.8% | Medium confidence | GO |
|
||
|
|
| **Cumulative gain** | +2.3% | Conservative | GO |
|
||
|
|
|
||
|
|
### Phase 46 Targets (Aggressive)
|
||
|
|
|
||
|
|
| Metric | Target | Expected | Status |
|
||
|
|
|--------|--------|----------|--------|
|
||
|
|
| **Throughput** | 62.6M ops/s | 59.66M + 5.0% | CONDITIONAL |
|
||
|
|
| **Gain from Opp A** | +2.5% | High confidence | GO |
|
||
|
|
| **Gain from Opp B** | +1.5% | Medium confidence | GO |
|
||
|
|
| **Gain from Opp C** | +1.0% | Low confidence | A/B TEST |
|
||
|
|
| **Cumulative gain** | +5.0% | Aggressive | MEASURE FIRST |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**Phase 45: COMPLETE (Analysis-only, zero code changes)**
|