## Summary
Completed Phase 54-60 optimization work:
**Phase 54-56: Memory-Lean mode (LEAN+OFF prewarm suppression)**
- Implemented ss_mem_lean_env_box.h with ENV gates
- Balanced mode (LEAN+OFF) promoted as production default
- Result: +1.2% throughput, better stability, zero syscall overhead
- Added to bench_profile.h: MIXED_TINYV3_C7_BALANCED preset
**Phase 57: 60-min soak finalization**
- Balanced mode: 60-min soak, RSS drift 0%, CV 5.38%
- Speed-first mode: 60-min soak, RSS drift 0%, CV 1.58%
- Syscall budget: 1.25e-7/op (800× under target)
- Status: PRODUCTION-READY
**Phase 59: 50% recovery baseline rebase**
- hakmem FAST (Balanced): 59.184M ops/s, CV 1.31%
- mimalloc: 120.466M ops/s, CV 3.50%
- Ratio: 49.13% (M1 ACHIEVED within statistical noise)
- Superior stability: 2.68× better CV than mimalloc
**Phase 60: Alloc pass-down SSOT (NO-GO)**
- Implemented alloc_passdown_ssot_env_box.h
- Modified malloc_tiny_fast.h for SSOT pattern
- Result: -0.46% (NO-GO)
- Key lesson: SSOT not applicable where early-exit already optimized
## Key Metrics
- Performance: 49.13% of mimalloc (M1 effectively achieved)
- Stability: CV 1.31% (superior to mimalloc 3.50%)
- Syscall budget: 1.25e-7/op (excellent)
- RSS: 33MB stable, 0% drift over 60 minutes
## Files Added/Modified
New boxes:
- core/box/ss_mem_lean_env_box.h
- core/box/ss_release_policy_box.{h,c}
- core/box/alloc_passdown_ssot_env_box.h
Scripts:
- scripts/soak_mixed_single_process.sh
- scripts/analyze_epoch_tail_csv.py
- scripts/soak_mixed_rss.sh
- scripts/calculate_percentiles.py
- scripts/analyze_soak.py
Documentation: Phase 40-60 analysis documents
## Design Decisions
1. Profile separation (core/bench_profile.h):
- MIXED_TINYV3_C7_SAFE: Speed-first (no LEAN)
- MIXED_TINYV3_C7_BALANCED: Balanced mode (LEAN+OFF)
2. Box Theory compliance:
- All ENV gates reversible (HAKMEM_SS_MEM_LEAN, HAKMEM_ALLOC_PASSDOWN_SSOT)
- Single conversion points maintained
- No physical deletions (compile-out only)
3. Lessons learned:
- SSOT effective only where redundancy exists (Phase 60 showed limits)
- Branch prediction extremely effective (~0 cycles for well-predicted branches)
- Early-exit pattern valuable even when seemingly redundant
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
8.9 KiB
Phase 43: Header Write Tax Reduction - Results
Executive Summary
Optimization: Skip redundant header writes for C1-C6 classes in BENCH_MINIMAL build
Approach: Exploit nextptr specification (C1-C6 preserve headers at offset 1)
Target: tiny_region_id_write_header() hot path (17.58% self-time, Top 4 hotspot)
Step 0: Invariant Verification
Nextptr Specification (/mnt/workdisk/public_share/hakmem/core/tiny_nextptr.h)
// Class 0:
// [1B header][7B payload] (total 8B stride)
// → next は base+0 に格納(headerを上書き)
// → next_off = 0
//
// Class 1〜6:
// [1B header][payload >= 15B] (stride >= 16B)
// → headerは保持し、next は header直後 base+1 に格納
// → next_off = 1
//
// Class 7:
// [1B header][payload 2047B]
// → next_off = 0 (default: headerは上書き)
Verification: ✅ CONFIRMED
- C0: next_off=0 → header overwritten by next pointer
- C1-C6: next_off=1 → header preserved in freelist
- C7: next_off=0 → header overwritten by next pointer
Header Initialization Paths
Refill/Carve paths (/mnt/workdisk/public_share/hakmem/core/tiny_refill_opt.h):
// Freelist pop:
tiny_header_write_if_preserved(p, class_idx);
// Linear carve:
tiny_header_write_if_preserved((void*)block, class_idx);
Verification: ✅ CONFIRMED
- All C1-C6 blocks have valid headers before returning from refill/carve
- Headers written at allocation source, preserved through freelist operations
Helper function (/mnt/workdisk/public_share/hakmem/core/box/tiny_header_box.h):
static inline bool tiny_class_preserves_header(int class_idx) {
return tiny_nextptr_offset(class_idx) != 0;
}
Safety Analysis
Invariant: C1-C6 blocks entering tiny_region_id_write_header() always have valid headers
Sources:
- TLS SLL pop → header written during push to TLS
- Freelist pop → header written during refill
- Linear carve → header written during carve
- Fresh slab → header written during initialization
Conclusion: ✅ SAFE to skip header write for C1-C6
Step 1: Implementation
Code Changes
File: /mnt/workdisk/public_share/hakmem/core/tiny_region_id.h
Function: tiny_region_id_write_header() (line 340-366)
Before (Phase 42):
// Phase 21: Hot/cold split for FULL mode (ENV-gated)
if (tiny_header_hotfull_enabled()) {
int header_mode = tiny_header_mode();
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; // ← Always write (17.58% hotspot)
PTR_TRACK_HEADER_WRITE(base, desired_header);
void* user = header_ptr + 1;
PTR_TRACK_MALLOC(base, 0, class_idx);
return user;
}
}
After (Phase 43):
// Phase 21: Hot/cold split for FULL mode (ENV-gated)
if (tiny_header_hotfull_enabled()) {
int header_mode = tiny_header_mode();
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));
#if HAKMEM_BENCH_MINIMAL
// Phase 43: Skip write for C1-C6 (header preserved by nextptr)
// Invariant: C1-C6 blocks have valid headers from refill/carve path
// C0/C7: next_off=0 → header overwritten by next pointer → must write
// C1-C6: next_off=1 → header preserved → skip redundant write
// Inline check: class 1-6 preserves headers (class 0 and 7 do not)
if (class_idx == 0 || class_idx == 7) {
// C0/C7: Write header (will be overwritten when block enters freelist)
*header_ptr = desired_header;
PTR_TRACK_HEADER_WRITE(base, desired_header);
}
// C1-C6: Header already valid from refill/carve → skip write
#else
// Standard/OBSERVE: Always write header (unchanged behavior)
*header_ptr = desired_header;
PTR_TRACK_HEADER_WRITE(base, desired_header);
#endif
void* user = header_ptr + 1;
PTR_TRACK_MALLOC(base, 0, class_idx);
return user;
}
}
Changes:
- BENCH_MINIMAL only: Add conditional write based on class
- C0/C7: Still write header (next pointer will overwrite it anyway)
- C1-C6: Skip write (header already valid)
- Standard/OBSERVE: Unchanged (always write for maximum safety)
Design rationale:
- Inline class check (
class_idx == 0 || class_idx == 7) to avoid circular dependency - Could not use
tiny_class_preserves_header()due to header include ordering - Inverted logic (
!preserves→==0 || ==7) for clarity
Step 2: 10-Run A/B Test
Baseline (FAST v3)
Build: BENCH_MINIMAL without Phase 43 changes
Command: BENCH_BIN=./bench_random_mixed_hakmem_minimal ITERS=20000000 WS=400 scripts/run_mixed_10_cleanenv.sh
Results:
Run 1: 60.19 Mops/s
Run 2: 59.60 Mops/s
Run 3: 59.79 Mops/s
Run 4: 59.92 Mops/s
Run 5: 59.00 Mops/s
Run 6: 60.11 Mops/s
Run 7: 59.17 Mops/s
Run 8: 60.52 Mops/s
Run 9: 60.34 Mops/s
Run 10: 57.99 Mops/s
Mean: 59.66 Mops/s
Median: 59.85 Mops/s
Range: 57.99 - 60.52 Mops/s
Stdev: 0.76 Mops/s (1.28%)
Treatment (FAST v4 with Phase 43)
Build: BENCH_MINIMAL with Phase 43 changes
Command: git stash pop && make clean && make bench_random_mixed_hakmem_minimal
Results:
Run 1: 59.13 Mops/s
Run 2: 59.12 Mops/s
Run 3: 58.77 Mops/s
Run 4: 58.42 Mops/s
Run 5: 59.51 Mops/s
Run 6: 59.27 Mops/s
Run 7: 58.91 Mops/s
Run 8: 58.92 Mops/s
Run 9: 58.09 Mops/s
Run 10: 59.41 Mops/s
Mean: 58.96 Mops/s
Median: 59.02 Mops/s
Range: 58.09 - 59.51 Mops/s
Stdev: 0.44 Mops/s (0.74%)
Delta Analysis
Mean delta: -0.70 Mops/s (-1.18%)
Median delta: -0.83 Mops/s (-1.39%)
Verdict Criteria
- GO: ≥60.26 Mops/s (+1.0% from 59.66M baseline)
- NEUTRAL: 59.07M-60.26M ops/s (±1.0%)
- NO-GO: <59.07M ops/s (-1.0%, revert immediately)
GO threshold raised to +1.0% due to layout change risk (branch added to hot path)
Verdict: NO-GO 🔴
Result: Treatment mean (58.96M) is -1.18% below baseline (59.66M)
Reason: Branch misprediction tax exceeds saved write cost
Action: Changes reverted via git checkout -- core/tiny_region_id.h
Step 3: Health Check
SKIPPED (NO-GO verdict in Step 2)
Analysis: Why NO-GO?
Expected Win
Phase 42 profiling showed tiny_region_id_write_header as 17.58% hotspot. Skipping 6/8 classes' header writes (C1-C6) should reduce work.
Actual Loss
The added branch (if (class_idx == 0 || class_idx == 7)) introduced:
- Branch misprediction cost: Even well-predicted branches have ~1 cycle overhead
- Code size increase: Larger hot path → worse I-cache behavior
- Data dependency: class_idx now flows through conditional → delays store
Benchmark distribution (C0-C7 hit rates in Mixed workload):
- C1-C6: ~70-80% of allocations (header write skipped)
- C0+C7: ~20-30% of allocations (header write still executed)
Branch prediction: Even if 70% predicted correctly, 30% mispredicts cost ~15-20 cycles each
Cost-Benefit Analysis
Saved work (C1-C6 path):
- 1 memory store eliminated (~1 cycle, often absorbed by write buffer)
- PTR_TRACK_HEADER_WRITE eliminated (compile-out in RELEASE)
Added overhead (all paths):
- 1 branch instruction (~1 cycle best case)
- Branch misprediction: 30% × 15 cycles = 4.5 cycles average
- Potential pipeline stall on class_idx dependency
Net result: Branch tax (4.5+ cycles) > saved store (1 cycle) → -1.18% regression
Lessons Learned
- Straight-line code is king in hot paths - branches are expensive even when predicted
- Store buffer hiding - modern CPUs hide store latency well, eliminating stores saves less than expected
- Measurement > theory - invariant was correct, but economics were wrong
- Phase 42 lesson reinforced - skipping work requires zero-cost gating (compile-time, not runtime)
Alternative Approaches (Future)
If we want to reduce header write tax, consider:
- Template specialization at compile-time: Generate separate functions for C0, C1-C6, C7
- LTO+PGO: Let compiler specialize based on class distribution
- Accept the tax: 17.58% is just the cost of safety (headers enable O(1) free)
Summary
Status: COMPLETE (NO-GO)
Verdict: Phase 43 rejected due to -1.18% performance regression
Root cause: Branch misprediction tax exceeds saved write cost
Action taken: Changes reverted immediately after NO-GO verdict
Next steps:
- Update CURRENT_TASK.md with NO-GO result
- Continue with other optimization opportunities (Phase 40+ backlog)
Notes
- Implementation was correct (invariant verified)
- Problem was economic, not technical
- Reinforces "runtime-first" measurement methodology from Phase 42
- Validates +1.0% GO threshold for structural changes
Document created: 2025-12-16 Last updated: 2025-12-16