# Phase 13: Header Write Elimination v1(C7 Header-Preserving Freelist) **Date**: 2025-12-14 **Status**: DESIGN(Phase 13 kickoff)→ ⚪ **NEUTRAL (+0.78%)**(research box freeze, default OFF) --- ## 0. Executive Summary(1枚) Phase 12 の比較で **system malloc (glibc) が hakmem より +63.7% 速い**ことが判明し、次の大きい構造差として **“steady-state のヘッダ書き込み(write tax)”** が最優先仮説になった。 ただし hakmem は free の hot path で `HEADER_MAGIC` を前提に **ヘッダを読む**ため、ヘッダを “無くす/壊す” と安全性が崩れる。 そこで Phase 13 v1 は「ヘッダ自体は維持」しつつ、**C7 の freelist でヘッダを上書きしない**設計に寄せて、既存の **E5-2 (Header write-once)** を **C7 にも適用可能にする**。 狙い: - C1-C6 は既に write-once で “alloc 時ヘッダ書き込み” をスキップ可能 - **C7 は現状 “free の next がヘッダを潰す” ため、alloc で毎回ヘッダ再書き込みが必要** - C7 の next を **base+1(user 先頭)**へ移すとヘッダが保持され、write-once で alloc 側の再書き込みを削れる --- ## 1. 現状(なぜ C7 だけ毎回書いているのか) ### 1.1 重要な前提(現行の正) - Free hot path(例: `core/front/malloc_tiny_fast.h` の `free_tiny_fast()`)は、 - `ptr-1` の `HEADER_MAGIC` を検証し - class_idx を header から抽出している → **ヘッダの正しさは safety と fast path の前提** ### 1.2 E5-2 (Header write-once) の適用範囲 - `core/box/tiny_header_box.h` の `tiny_header_finalize_alloc()` が、 - `HAKMEM_TINY_HEADER_WRITE_ONCE=1` かつ - `tiny_class_preserves_header(class_idx)=true`(C1-C6) のとき、alloc 時の `tiny_region_id_write_header()` をスキップする。 ### 1.3 C7 が write-once にならない理由(根本) - `core/box/tiny_layout_box.h` の `tiny_nextptr_offset()` が - C7 は `next_off=0`(= `base+0` に next を書く) → free 時に **ヘッダ領域を next pointer で上書き**する → alloc で必ず `tiny_region_id_write_header()` を実行し直す必要がある (C0 も同じだが、C0 は stride 8B のため `base+1` に 8B next を置けない制約がある) --- ## 2. 提案(Phase 13 v1) ### 2.1 変更のコア **C7 の next pointer を `base+1`(user 先頭)に移す**: - Before(現行): - C7: `next_off=0` → `*(void**)base = next`(ヘッダ破壊) - After(Phase 13 v1): - C7: `next_off=1` → `memcpy(base+1, &next, 8)`(ヘッダ保持) これにより C7 が “header-preserving class” になり、E5-2 の write-once が C7 にも効く。 ### 2.2 Box Theory(箱割り) ``` L0: tiny_c7_preserve_header_env_box (ENV gate, A/B, refresh) ↓ L1: tiny_layout_box (tiny_nextptr_offset の SSOT) ↓ L2: tiny_nextptr (next load/store は SSOT を参照) ↓ L3: tiny_header_box (class_preserves_header → write-once 適用) ``` 境界は 1 箇所: - 「C7 の next オフセット決定」= `tiny_nextptr_offset()` に集約(他で分岐しない) ### 2.3 戻せる(A/B) - ENV: `HAKMEM_TINY_C7_PRESERVE_HEADER=0/1`(default: 0) - まずは research box として導入し、GO なら preset 昇格 --- ## 3. Safety / Invariants(Fail-Fast) ### 3.1 不変条件 - `tiny_next_store/load` は **常に** `tiny_nextptr_offset()` を参照(直書き禁止) - `tiny_class_preserves_header(class_idx)` は offset!=0 で決まる(ハードコード禁止) - C7 preserve ON のとき: - free 後も `*(uint8_t*)base == HEADER_MAGIC|cls` が保持される(ヘッダ破壊が起きない) ### 3.2 Fail-Fast(debug 限定) - デバッグのみ、C7 preserve ON のときに: - `tiny_header_validate(base, 7, ...)` の mismatch をワンショットで出す - release では常時ログ無し、必要なら stats カウンタのみ --- ## 4. A/B 計測計画(同一バイナリ) この変更は “freelist next の配置” を変えるため、本来は layout 差になるが、Phase 13 v1 は **ENV で切替**できるようにして同一バイナリ A/B を維持する(Phase 5-7 の教訓)。 ### 4.1 4点マトリクス(必須) | Case | HAKMEM_TINY_C7_PRESERVE_HEADER | HAKMEM_TINY_HEADER_WRITE_ONCE | 意味 | |------|--------------------------------|-------------------------------|------| | A | 0 | 0 | 現行 baseline | | B | 0 | 1 | E5-2 のみ(C1-C6) | | C | 1 | 0 | C7 next を user に移す(ヘッダは毎回書く) | | D | 1 | 1 | Phase 13 v1 本命(C1-C7 を write-once) | ### 4.2 GO/NO-GO(Mixed 10-run) - GO: mean **+1.0% 以上** - NO-GO: mean **-1.0% 以下** - NEUTRAL: ±1.0% → freeze(research box) --- ## 5. リスクと対策 ### リスク 1: C7 next が unaligned になり memcpy 経由で遅くなる - 対策: Case C(write-once 無し)を必ず測り、layout 変更単体のコストを分離する - もし C が大きく負ける場合: - “C7 next offset=8(aligned)” の派生案を検討(Phase 13 v1b) ### リスク 2: class_idx ハードコードが残っていて壊れる - 対策: `rg "== 7|!= 7|C7 uses offset 0"` を掃除し、SSOT(`tiny_layout_box`)参照に寄せる ### リスク 3: ENV refresh が bench_profile putenv に追従しない - 対策: Phase 8 と同様に `*_env_refresh_from_env()` を用意し、`bench_profile.h` から呼ぶ --- ## 6. 次(Phase 13 以降の視界) Phase 13 v1 は「ヘッダを “消す”」ではなく「**steady-state のヘッダ再書き込みを減らす**」に寄せる。 もし system malloc との差がまだ大きい場合、次の大テーマは: - Thread cache(tcache 相当の構造)を TinyUnifiedCache に移植する(Phase 14 候補)