diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 38c54d02..734c0ee4 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,1855 +1,93 @@ -# Current Task (2025-11-07) +# Current Task – 2025-11-08 -## 🔴 Phase 6-2.4: SuperSlab Guess Loop SEGV Fix (IN PROGRESS) +## ✅ 完了: 二重割り当てバグの修正 -### 問題 -`bench_random_mixed_hakmem` と `bench_mid_large_mt_hakmem` が即座に SEGV +### 根本原因 +`trc_linear_carve()` が `meta->used` をカーソルとして使用していたが、`meta->used` はブロック解放時に減少するため、既に割り当て済みのブロックが再度カーブされる**二重割り当てバグ**が発生していた。 -**再現:** +### 発見プロセス +1. Fail-Fast 診断ログで TLS SLL head の破壊を検出 (`0x...fcf1` = 241バイトの misalignment) +2. 配布ポインタの next 読み取り時にユーザーデータ (`0x6261` = ASCII "ab") を検出 +3. ログ解析で同じブロック (offset 0xFC00 = 64512) が 2回割り当てられていることを確認 +4. LINEAR_CARVE ログで `used=61 batch=1` → `used=59 batch=3` の矛盾を発見 + +### 証拠ログ +``` +[LINEAR_CARVE] base=0x...600800 used=61 batch=1 → ブロック 61 を作成 +(いくつかのブロックが解放され、used が 62 → 59 に減少) +[LINEAR_CARVE] base=0x...600800 used=59 batch=3 → ブロック 59, 60, 61 を再作成! +``` + +### 実装した修正 + +**1. `TinySlabMeta` 構造体に `carved` フィールド追加** (`core/superslab/superslab_types.h`) +```c +typedef struct TinySlabMeta { + void* freelist; + uint16_t used; // 現在使用中のブロック数(増減両方) + uint16_t capacity; + uint16_t carved; // 線形領域からカーブしたブロック数(単調増加のみ) + uint16_t owner_tid; // uint32_t → uint16_t に変更 +} TinySlabMeta; +``` + +**2. `trc_linear_carve()` を修正** (`core/tiny_refill_opt.h`) +```c +// Before: meta->used をカーソルとして使用(バグ!) +uint8_t* cursor = base + ((size_t)meta->used * bs); +meta->used += batch; + +// After: meta->carved をカーソルとして使用(修正版) +uint8_t* cursor = base + ((size_t)meta->carved * bs); +meta->carved += batch; // 単調増加のみ +meta->used += batch; // 使用中カウントも更新 +``` + +**3. 初期化コード追加** (`core/hakmem_tiny_superslab.c`) +```c +meta->carved = 0; // carved カウンター初期化 +meta->owner_tid = (uint16_t)owner_tid; // uint16_t にキャスト +``` + +### テスト結果 ```bash -./bench_random_mixed_hakmem 50000 2048 1234567 # → Exit 139 (SEGV) -./bench_mid_large_mt_hakmem 2 10000 512 42 # → Exit 139 (SEGV) +# Fail-fast モード(診断ログあり) +HAKMEM_TINY_REFILL_FAILFAST=2 ./bench_random_mixed_hakmem 50000 2048 1234567 +→ ✅ 651,627 ops/s (クラッシュなし) + +# 通常モード(診断ログなし) +./bench_random_mixed_hakmem 50000 2048 1234567 +→ ✅ 950,037 ops/s (クラッシュなし) ``` -### 根本原因(Ultrathink Task完全解析) +### 修正されたファイル +- `core/superslab/superslab_types.h` - `TinySlabMeta` に `carved` フィールド追加 +- `core/tiny_refill_opt.h` - `trc_linear_carve()` を `carved` 使用に修正 +- `core/hakmem_tiny_superslab.c` - `carved` 初期化 +- `core/tiny_alloc_fast.inc.h` - 診断ログ追加 (次ポインタ検証) +- `core/hakmem_tiny_free.inc` - 診断ログ追加 (drain/free 検証) -**Location:** `core/box/hak_free_api.inc.h:92-95` (guess loop) +## 次のステップ -**The Bug:** -```c -for (int lg=21; lg>=20; lg--) { - SuperSlab* guess=(SuperSlab*)((uintptr_t)ptr & ~mask); - if (guess && guess->magic==SUPERSLAB_MAGIC) { // ← SEGV - // guess が unmapped memory → dereference で SEGV - } -} -``` +1. **診断ログのクリーンアップ** (Optional) + - Fail-Fast ログを本番向けに最適化 + - デバッグ用のログを削除 -**Why SEGV:** -1. Registry lookup 失敗(alloc が SuperSlab 以外から) -2. Guess loop で 1MB/2MB align した `guess` を計算 -3. メモリマップ検証なし -4. `guess->magic` で unmapped memory dereference → **SEGV** +2. **性能ベンチマーク** + - `bench_random_mixed` でスループット計測 + - Larson ベンチマークで検証 + - System/mimalloc との比較 -**Why Benchmark Differences:** -- **Larson** (✅ works): All from SuperSlab → registry hit → guess loop スキップ -- **random_mixed** (❌ SEGV): Diverse sizes → non-SuperSlab allocs → guess loop → SEGV -- **mid_large_mt** (❌ SEGV): Large allocs → non-SuperSlab → guess loop → SEGV +3. **追加テスト** + - マルチスレッドストレステスト + - 長時間実行テスト -**Why LD_PRELOAD Works:** -- `hak_core_init.inc.h:119-121` で SuperSlab をデフォルト無効化 -- → SS-first path スキップ → guess loop 回避 → SEGV なし - -### 修正試行1: Guess Loop 削除 ❌ - -**Applied:** `core/box/hak_free_api.inc.h:92-95` 削除 - -**Result:** Still SEGV(別の箇所に問題あり) - -### 次のアクション -- [ ] SEGV の新しい場所を特定(gdb/ASan) -- [ ] Registry lookup が失敗する根本原因を調査 -- [ ] Complete report: `SEGV_ROOT_CAUSE_COMPLETE.md` - -### 🐛 新しい観測 (2025-11-07 19:10) - -- `HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0` で `bench_random_mixed_hakmem` を gdb 監視。class 7 の `TinySlabMeta` (`meta@0x7ffff6a00060`) にハードウェアウォッチを張ったところ、`sll_refill_batch_from_ss` 内で `meta->freelist` が `0x00000000000000e2` に化ける瞬間を捕捉。 -- 同じ SuperSlab 上の実ブロック (例: `0x7ffff6a3ec00`) の先頭ワードにウォッチを追加すると、`hak_tiny_free_superslab` → `trc_splice_to_sll` までは正しい next ポインタが書かれているが、その後ユーザープログラム (`bench_random_mixed.c` が `slot[idx][0] = ...` を書く地点) で 1byte 書き込みが入り、先頭ワードが `0xe2` に上書きされる。 -- つまり「まだアプリに貸し出しているブロック」が freelist に再露出しており、`sll_refill_batch_from_ss` が `*(void**)node` を読んだ瞬間に利用者データをポインタとして扱って SEGV になっている。 -- 該当 run では direct freelist push (`hak_tiny_free_superslab`) で `*(void**)ptr = prev` が実行されているログも取れているため、別経路(TLS spill / bg spill / remote drain)で stale head が復活している可能性が高い。 -- Fail-Fast instrumentation(`trc_pop_from_freelist()` に SuperSlab 範囲チェックを追加)を入れたところ、`[TRC_FAILFAST] stage=freelist_next cls=7 node=0x7eab7b20fc53 base=0x7eab7b200000 limit=0x7eab7b400000` で即座に abort。`meta->freelist` からポップしたノードの「次ポインタ」が SuperSlab 範囲外(= ユーザー書き換え)であることが確認できた。 -- `HAKMEM_TINY_REFILL_FAILFAST=2` で `tiny_failfast_log()` を `hak_tiny_free_superslab` / TLS spill / BG spill / remote drain の各箇所に挿入。ログを見ると **すべての問題ノードが `stage=free_local_box`(= 同一スレッド free)で登録** されており、`node=0x7e37d560fc53` のように **64B 非整列のアドレスが freelist に入っている** ことがわかった。 -- `HAKMEM_TINY_BG_SPILL=0` / `HAKMEM_TINY_TLS_LIST=0` / `HAKMEM_TINY_FAST_CAP=0` などの箱単位 A/B を実施しても Fail-Fast は継続。クラス 7 だけでなくクラス 6 でも `node=0x15` のように壊れた値が freelist に現れるため、原因は spill/remote ではなく「同一スレッド free に渡ってくる `ptr` 自体が壊れている(= ユーザが持っているポインタが既にズレている)」ラインが濃厚。 -- `tiny_free_local_box()` に SuperSlab/アライン検証を噛ませ、さらに `tiny_debug_track_alloc_ret()` でも配布直後のポインタを検証した結果、**割り当て段階で既に壊れたポインタを返している** ことが確定。 - - 例: `[TRC_FAILFAST_PTR] stage=alloc_ret_range cls=7 slab_idx=0 ptr=0x7ffff6a0fc00 reason=out_of_capacity base=0x7ffff6a00000 limit=0x7ffff6c00000 cap=63 used=63 offset=64512`(= capacity 個目のブロック)。 - - 例: `[TRC_FAILFAST_PTR] stage=alloc_ret_align cls=7 slab_idx=0 ptr=0x7ffff6a0f835 reason=misaligned ... cap=63 used=62 offset=63541`(1024B 未満の端数 709 を含む異常アドレス)。 - → `meta->used` と `meta->capacity` の境界処理、または `slab_idx==0` のヘッダ調整あたりで off-by-one / 加算漏れが起きており、存在しないブロックを線形 carve で組み立てている疑いが濃厚。 - -**次のアクション** -1. `sll_refill_batch_from_ss` に Fail-Fast を追加し、`meta->freelist` / `*(void**)node` が SuperSlab 範囲・アラインメント外だった場合に即座にログ&アボート(class, slab_idx, node, next, remote_heads も記録)。 -2. `hak_tiny_free_superslab` / `tls_list_spill_excess` / `bg_spill_drain_class` など `meta->freelist = node` を行う箇所で、`prev` が当該 SuperSlab 範囲かどうかをチェックするワンショットログを差し込み、どの経路で stale pointer が混入しているか切り分ける。 -3. 計測時に `HAKMEM_TINY_BG_SPILL=0`, `HAKMEM_TINY_TLS_LIST=0`, `HAKMEM_TINY_FAST_CAP` などを個別に OFF にして A/B。どの front/ spill 経路が二重登録を起こすかを特定してから修正を入れる。 - ---- - -## 📊 ベンチマーク行列サマリ (2025-11-07) - -実施: Larson + Suite(random_mixed / mid_large_mt / vm_mixed / tiny_hot)を system / mimalloc / HAKMEM で横並び計測し、CSV保存。 -- 保存先(Larson): `bench_results/larson/20251107_131427/results.csv` -- 保存先(Suite) : `bench_results/suite//results.csv` - -要点(この環境) -- Larson 4T: HAKMEM ≒ system ≒ mimalloc(≈3.35M ops/s)→ 上限到達(勝ち) -- Larson 1T: HAKMEM は差 ≈9–11%(3.03M vs 3.35M)→ 詰めれば勝ち筋 -- random_mixed(16–1024B): HAKMEM ≪ system/mimalloc(例: 5.9M vs 53–56M)→ 大差(要対策) -- mid_large_mt(8–32KiB, MT): HAKMEM ≪ system/mimalloc(1.05M vs 8.8–9.0M)→ 大差(要対策) -- vm_mixed(512KB–<2MB): HAKMEM ≪ system(~0.137M)→ 大差(要対策) -- tiny_hot(32B/64B): HAKMEM 80–85M vs system/mimalloc ~181–186M → 1/2水準(要対策) - -ログとスクリプト -- Larson 行列実行: `scripts/bench_larson_matrix.sh`(CSV + raw 保存) -- Suite 一括実行: `scripts/bench_suite_matrix.sh`(CSV + raw 保存) - ---- - -## 🏁「全部勝つ」プラン(優先度順の打ち手) - -Phase A(即効A/B、1日) -- Larson 1T(9–11%差の解消) - - 特化分岐 ON: `HAKMEM_TINY_SPECIALIZE_MASK=0x0F`(8/16/32/64B)で branch 減(Box 5) - - adopt=OFF(1T)、FAST_CAP=16/32 A/B、PGO(tiny_hot/larson)で最短パス強化 - - 期待: 3.03M → 3.10–3.18M(system 3.35M に接近) - -- tiny_hot(80M → 120M+ を目標) - - Strict Front/branchless pop の微最適化(Box 5 内だけ、境界不変) - - SLL cap/REFILL のホット帯 A/B(`REFILL_COUNT_HOT=48/64`, `FAST_CAP=16/32`) - - 期待: +30–40%(単体ベンチでの指標) - -Phase B(中規模、2–3日) -- random_mixed(5.9M → 30–40M を目標) - - TLS/SLL ヒット率向上(Front‑Gate Box の早期 return、MAG→SLL 経路の分岐簡素化) - - free 経路の境界コスト削減(Box 2/3 内で副作用封じ、Box 4 で一括処理) - - 特化 + PGO の組み合わせを sweep(スクリプト化) - -- mid_large_mt(1.05M → 6–8M を目標) - - L2.5/Large のバッチ/Flush/Harvest のチューニング(箱内のみ、境界不変) - - Back‑pressure(bg remote / flush 閾値)を MT に合わせて最適化 - - 大サイズ再利用の BigCache/L25 ゲートを A/B(`HAKMEM_BIGCACHE_*`) - -- vm_mixed(~0.137M → 1–2M を目標) - - 512KB–<2MB 帯の再利用強化(BigCache‑L25 方向) - - mmap/munmap 頻度低減のためのバッチ化・しきい値調整(箱内) - -Phase C(検証と固定化) -- 各ベンチで 5–10 回の連続実行 → 中央値を CSV 追記、グラフ化 -- 勝ち構成を `bench_results_archive/` に保存、ENV プリセット化(profiles/*.env) - ---- - -## TODO(実行リスト:当面のアクション) -- [ ] Larson 1T: SPECIALIZE_MASK/FAST_CAP/PGO の A/B を実施し CSV 追記 -- [ ] tiny_hot: Strict Front + REFILL/HOT の sweep(32/64B) -- [ ] random_mixed: Front‑Gate Box の早期 return A/B、free 境界軽量化 A/B -- [ ] mid_large_mt: L25/BigCache の閾値・バッチ・bg_remote A/B -- [ ] vm_mixed: L2.5 帯の再利用ゲート/バッチ化 A/B -- [ ] スイート行列(scripts/bench_suite_matrix.sh)の繰返し回数を増やし中央値取得 - -## ✅ Phase 6-2.4: SuperSlab SEGV fix(2025-11-07) - -現象(修正前) -- Tiny alloc は成功するが、free 時に SuperSlab を見つけられず `magic=0` → invalid pointer → SEGV。 -- Direct-link と LD_PRELOAD で挙動が異なり、前者は `g_invalid_free_mode=1` により skip→リーク→崩壊、後者は libc へフォールバックで一見動作(実際はリーク)。 - -修正内容 -- core/box/hak_free_api.inc.h - - Guess ループ(未マップ領域への生読み取り)を削除。 - - Header 参照前に `hak_is_memory_readable()`(mincore ベース、fallback のみで使用)で可読性を確認。 -- core/hakmem_internal.h - - `hak_is_memory_readable(void*)` を追加(mincore 1byte で可読チェック)。 -- レジストリの同期も正規化済(Phase 6-2.3 補強):`SuperRegEntry.base` を atomic 化、acq/rel 統一。 - -検証(直近実測) -- random_mixed(cycles=200k, ws=4096): 2.84M ops/s(修正前: SEGV) -- random_mixed(cycles=400k, ws=8192): 2.92M ops/s(修正前: SEGV) -- mid_large_mt(threads=4, cycles=40k, ws=2048): 2.00M ops/s(修正前: SEGV) -- Larson 4T: 0.838M ops/s → 0.838M ops/s(変化なし、安定) - -備考 -- mincore は fallback 経路のみで使用され、ホットパスに入らないため性能影響は無視できる水準。 -- 追加のデバッグノブ:`HAKMEM_SUPER_REG_DEBUG=1`(register/unregister 一発)/ `HAKMEM_SUPER_REG_REQTRACE=1`(invalid-magic 時に 1MB/2MB base+magic を一発) - - -## 🔍 SuperSlab registry デバッグ進捗 (2025-11-07) -- ✅ `SuperRegEntry.base` を `_Atomic uintptr_t` 化し、登録/解除/lookup で acquire/release を正規化。 -- ✅ 追加ノブ: - - `HAKMEM_SUPER_REG_DEBUG=1` → register/unregister を1行ログ出力(例: `[SUPER_REG] register base=...`)。 - - `HAKMEM_SUPER_REG_REQTRACE=1` → invalid-magic 時に 1MB/2MB の base/magic を一発表示。 - -現状観測 -- `bench_random_mixed_hakmem` / `bench_mid_large_mt_hakmem` は短ランでもセグフォ再現。stderr 冒頭は初期化ログと大量の `[SUPER_REG] register ...` のみで、unregister は未視認。 -- `HAKMEM_SUPER_REG_REQTRACE=1` を ON にした直リンク短ランでは、現段階で `[SUPER_REG_REQTRACE] ...` 行は出ず(= header 経由の invalid-magic 発火前に崩れている)。 -- Asan PRELOAD (`LD_PRELOAD=libasan.so:libhakmem_asan.so`) で system 版を実行し stack/ログを `/tmp/asan_{rand,midmt,vm}_sys.*` に保存済み。次は stack 抽出と CURRENT_TASK への貼付を予定。 - -次ステップ -1. 直リンク短ラン + `HAKMEM_SUPER_REG_REQTRACE=1` + `SIGUSR2`(Tiny Debug Ring)の組合せで、`hak_super_lookup` 前後の順序を突き止める。 -2. `/tmp/asan_*` ログから `[SUPER_REG] register` の時系列と Asan stack を抽出し、free→lookup→unregister の競合がないか記録。 -3. 必要に応じて `hak_tiny_free` の入口に Fail-Fast(SLL上限/SS範囲アサート)を追加し、異常を早期に顕在化させる。 - -## 🚨 SuperSlab ON での直リンク random_mixed 再現 (2025-11-07) - -再現手順(直リンク・短ラン) -``` -env HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0 \ - ./bench_random_mixed_hakmem 200000 4096 1234567 -``` -結果: SEGV(EXIT 139)。stderr 先頭には [ELO]/[Batch]/[ACE] と大量の `[SUPER_REG] register ...` が出力されるが、`unregister` は未視認。 - -Asan PRELOAD での stack 採取(system 版) -``` -make -j asan-shared-alloc -env HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0 \ - LD_PRELOAD="$(gcc -print-file-name=libasan.so):$PWD/libhakmem_asan.so" \ - ASAN_OPTIONS="halt_on_error=1:abort_on_error=1:alloc_dealloc_mismatch=1:detect_leaks=0:fast_unwind_on_malloc=0" \ - ./bench_random_mixed_system 200000 4096 1234567 2> /tmp/asan_rand_ss.err -``` -期待: invalid free/lookup 近傍のバックトレース + `[SUPER_REG]` の登録ログを取得。 - -reqtrace + リング強制(SEGV前の痕跡) -``` -env HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0 \ - HAKMEM_SUPER_REG_REQTRACE=1 HAKMEM_SUPER_REG_DEBUG=1 \ - ./bench_random_mixed_hakmem 50000 2048 1234567 2> /tmp/rand_reqtrace_ss.err & pid=$!; \ - sleep 1; kill -USR2 $pid; wait $pid -``` - -メモ: 上記のログ抽出・stack 解析後、必要に応じて free 入口の Fail‑Fast 追加や、lookup の lg/align 判定の一時バイアスで切り分け予定。 - -## ✅ Phase 6-2.3: Active Counter Bug Fix (2025-11-07) - -### 問題発見 -HAKMEM 直リンク 4T で `free(): invalid pointer` クラッシュを発見: - -**再現手順:** +## 実行コマンド ```bash -./larson_hakmem 10 8 128 1024 1 12345 4 -# → Exit 134: free(): invalid pointer -# → [DEBUG] superslab_refill returned NULL (OOM) +# 通常テスト +HAKMEM_TINY_USE_SUPERSLAB=1 ./bench_random_mixed_hakmem 50000 2048 1234567 + +# Fail-fast 診断モード +HAKMEM_TINY_REFILL_FAILFAST=2 HAKMEM_TINY_USE_SUPERSLAB=1 \ + ./bench_random_mixed_hakmem 50000 2048 1234567 ``` - -**症状:** -- System malloc/mimalloc: 3.3M ops/s ✅ -- HAKMEM 1T: 838K ops/s (-75%) ⚠️ -- HAKMEM 4T: 起動直後にクラッシュ ❌ - -### 根本原因(Ultrathink Task Agent 調査) -**Active Counter Double-Decrement in P0 Batch Refill** - -`core/hakmem_tiny_refill_p0.inc.h:103` で freelist から TLS cache にブロックを移動する際、active counter をインクリメントし忘れ: - -``` -1. Free → カウンタ減算 ✅ -2. Remote drain → freelist に追加(カウンタ変更なし) ✅ -3. P0 batch refill → TLS に移動(カウンタ増加忘れ)❌ ← バグ! -4. 次の Free → カウンタ減算 ❌ ← ダブルデクリメント! -``` - -結果:カウンタアンダーフロー → SuperSlab が「満杯」 → OOM → クラッシュ - -### 修正内容 -**File:** `core/hakmem_tiny_refill_p0.inc.h:103` - -```diff - trc_splice_to_sll(class_idx, &chain, &g_tls_sll_head[class_idx], &g_tls_sll_count[class_idx]); --// NOTE: from_freelist は既に used/active 計上済みのブロックの再循環。 -+// FIX: Blocks from freelist were decremented when freed, must increment when allocated -+ss_active_add(tls->ss, from_freelist); -``` - -### 検証結果 - -| 設定 | 修正前 | 修正後 | -|------|--------|--------| -| デフォルト 4T | ❌ クラッシュ | ✅ 838K ops/s | -| 安定性(2回実行) | - | ✅ 同じスコア | - -### 残課題 -❌ **`HAKMEM_TINY_REFILL_COUNT_HOT=64` でクラッシュ再発** - -```bash -HAKMEM_TINY_REFILL_COUNT_HOT=64 ./larson_hakmem 10 8 128 1024 1 12345 4 -# → Exit 134: free(): invalid pointer (class=4 で OOM) -``` - -**暫定診断:** -- Class 0-3 が `want=64` で大量 refill → メモリ過剰消費 -- Class 4 がメモリ不足で OOM -- 原因: TLS cache 過剰蓄積またはメモリリーク? - -**次のアクション候補:** -1. TLS cache サイズ制限確認(`HAKMEM_TINY_FAST_CAP`) -2. メモリリーク詳細調査(valgrind) -3. デフォルト refill count と Class 0-3 vs 4 の比較 - ---- - -## 🎉 デバッグ完了 (2025-11-07 - Sanitizer) - -結論 -- HAKMEM allocator は ASan / UBSan で健全性を確認済み。メモリ破壊や未定義動作は検出されず、現状の箱境界は安全に動作。 - -検証ルート(便利ターゲット) -- ✅ `make asan-preload-run THREADS=4` — メモリ破壊チェック(安定; 非ASan本体+LD_PRELOADでASan先頭) -- ✅ `make ubsan-mailbox-run THREADS=4` — Mailbox/Remote 健全性チェック(安定) -- ✅ `make asan-preload-mailbox-lite THREADS=4` — 短時間の境界チェック(安定, 5s/CHPT=256, RemoteGuard+Ring) - -補足(運用ガイド) -- ASan直リンクの4Tは環境依存で Shadow 予約に失敗するため、当面は PRELOAD 方式を既定とし、Mailbox 有効系は UBSan を用いる。 -- CI/スクリプトは上記ターゲットを用いることで再起動ループの回避と安定検証が可能。 - -## 🆘 最優先: 強制終了/再起動ループの緊急対応(sanitizer/環境周り) - -現象 -- ベンチ実行や補助プロセス実行中に強制終了(SIGKILL/abort)し、環境全体が再起動ループに入ることがある。 -- ASan直リンク(`larson_hakmem_asan{,_alloc}`)の4T実行で高頻度に `ReserveShadowMemoryRange failed (ENOMEM)` が発生。 -- Ready/Mailbox ON + ASan ではプロセスが沈黙/強制終了するケースあり(stderr/outとも0バイト)。 - -暫定診断(根本原因候補) -- AddressSanitizer の Shadow メモリ確保が他領域と衝突し ENOMEM → ランタイムが abort。 -- LD_PRELOAD順序/ASLR/マップ数制限(`vm.max_map_count`)/LTO+最適化との相性で、MT下の Shadow 予約が不安定化。 -- Ready/Mailbox ON でメモリマップ挙動が変化し、ASan の reserve 失敗トリガを踏みやすい。 - -安全な回避経路(検証済み) -- ASan: 本体は非ASan(`larson_system`)、`LD_PRELOAD=$(gcc -print-file-name=libasan.so):./libhakmem_asan.so` でランタイム先頭ロード。 - - 4T 安定完走(Ready/Mailbox OFF): ~3.25M ops/s を確認。 -- UBSan: `make ubsan-larson-alloc` + Ready/Mailbox ON で 4T 完走(~3.35M ops/s)。 - -緊急対応の優先順位(Fail‑Safe) -1) 既定の「計測/検証」ルートを sanitizer-safe に切替(当面の安定化) - - ASanが必要: `scripts/run_larson_asan_preload.sh 4`(Ready/Mailbox OFF) - - Ready/Mailbox ON検証: UBSanに切替 `HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 ./larson_hakmem_ubsan_alloc …` -2) 再起動ループ回避の運用ガード - - 長時間ASan 4T禁止、まず短時間/小負荷(例: ch/thread 256, sleep 5)で段階実行。 - - CI/スクリプトで ASan 直リンク4Tをスキップし、LD_PRELOAD 方式へ統一。 -3) 根治(後追い・環境依存) - - オプション: `ASAN_OPTIONS=quarantine_size_mb=8:malloc_context_size=5` 等でメモリ圧縮(効果限定的)。 - - `vm.max_map_count` 引き上げ、ASLR制御、最適化/LTO無効化(要環境合意)。 - -当面の実行手順(コピペ可) -``` -# ASan(Ready/Mailbox OFF, 4T安定) -./scripts/run_larson_asan_preload.sh 4 - -# UBSan(Ready/Mailbox ON, 4T安定) -make -j ubsan-larson-alloc >/dev/null -HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 ./larson_hakmem_ubsan_alloc 10 8 128 1024 1 12345 4 -``` - -チェックリスト(PR/レビュー時) -- [ ] ASan直リンク4Tを実行していない(PRELOAD方式へ誘導)。 -- [ ] Ready/Mailbox ON の4Tは UBSan で計測している。 -- [ ] ベンチスクリプトで異常終了時に即時中断しループしない。 -- [ ] 影響範囲のログ(ワンショット/リング)を残すが常時多出力は避ける。 - -## 🔔 最新アップデート (2025-11-05 16:30) 🔥🔥🔥 - -### ✅ Registry 線形スキャン ボトルネック特定! - -**Perf 分析完了 → Root Cause 発見!** - -**測定結果 (Larson, threads=4):** -``` -HAKMEM: 3.62M ops/s (21.6% of system) -System: 16.76M ops/s (100%) -差: -78.4% 💀 -``` - -**Perf プロファイリング結果:** -``` -28.51% superslab_refill 💀💀💀 圧倒的ボトルネック - 2.58% exercise_heap (ベンチマーク本体) -``` - -**問題:** アロケータ (superslab_refill) がベンチマーク本体 (exercise_heap) より CPU time を消費! - -**Hot Instructions 分析 (perf annotate):** -``` -32.36% cmp $0x3ffff,%r11d ← 262,143 回ループ! -16.78% inc %r13d ← カウンタ++ -16.29% add $0x18,%rbx ← ポインタ進める -合計 97.65% の CPU time がループに集中 -``` - -**Root Cause (core/hakmem_tiny_free.inc:917):** -```c -for (int i = 0; i < SUPER_REG_SIZE && scanned < scan_max; i++) { - // ^^^^^^^^^^^ 262,144 エントリを線形スキャン! - SuperRegEntry* e = &g_super_reg[i]; - // ... class_idx 不一致でも全エントリをイテレート -} -``` - -**解決策: Per-class Registry** -```c -// Before: 全 class 混在 -SuperRegEntry g_super_reg[262144]; - -// After: class ごとに分離 -SuperRegEntry g_super_reg_by_class[TINY_NUM_CLASSES][4096]; -// 8 classes × 4096 entries = 32K total -``` - -**期待効果:** -- スキャン対象: 262,144 → 4,096 エントリ (-98.4%) -- **期待改善: +200-300%** (2.59M → 7.8-10.4M ops/s) -- **System malloc の 54-73% まで到達可能!** 🎯 - -**詳細レポート:** -- `PERF_ANALYSIS_2025_11_05.md` - 完全分析 + 実装プラン -- Commit: `859027e` "Perf Analysis: Registry 線形スキャンがボトルネック" -- Branch: `perf-analysis-2025-11-05` (pushed to private repo) - -**次のアクション:** -1. **Phase 1 実装** (1-2日): per-class registry に変更 - - `core/hakmem_super_registry.{h,c}` 構造変更 - - `core/hakmem_tiny_free.inc:917` スキャン簡素化 - - 目標: 2.59M → 7.8M ops/s (+3倍!) - -2. **Phase 2 実装** (追加 1日): 早期終了 + First-fit - - 最初の freelist 発見で即 return - - 目標: 7.8M → 11.7M ops/s (system の 82%!) - -**重要:** Box Refactor は既に動いている! -- Single-thread: 0.46M → 2.59M ops/s (+463%!) ✅ -- Multi-thread: 1.81M → 4.19M ops/s (+131%!) ✅ -- 外部AIの最適化も効いている (Option A: Inline TLS cache access) -- **ただし Registry スキャンがボトルネックで system には届かず** - ---- - -## 🔔 最新アップデート (2025-11-06) - -- Build 既定を Box Refactor(Phase 6-1.7)に切替済み。 - - Makefile に `-DHAKMEM_TINY_PHASE6_BOX_REFACTOR=1` を既定付与。 - - 旧系へ切替: `make BOX_REFACTOR_DEFAULT=0 larson_hakmem`。 -- Larson 2s/4T・5s/4T でセグフォ再発なしを確認(SLL-only, FAST_CAP=16, SS_ADOPT=1)。 -- 次フェーズ: mimalloc 対決(Larson)へ移行。Hot Tiny クラス向けの refill/binding 帯域最適化に集中。 - -推奨計測プロファイル(現時点) -- SLL-only Fast: `HAKMEM_TINY_TLS_SLL=1 HAKMEM_TINY_TLS_LIST=0 HAKMEM_TINY_HOTMAG=0` -- Fast tier: `HAKMEM_TINY_FAST_CAP=16` -- Refill: `HAKMEM_TINY_REFILL_COUNT_HOT=64`(A/B: 48/64) -- Pipeline: `HAKMEM_TINY_SS_ADOPT=1`(publish→mail→adopt 通電) - -再現コマンド(2s/4T, 5s/4T) -``` -HAKMEM_TINY_REFILL_COUNT_HOT=64 \ -HAKMEM_TINY_FAST_CAP=16 \ -HAKMEM_TINY_TRACE_RING=0 HAKMEM_SAFE_FREE=0 \ -HAKMEM_TINY_TLS_SLL=1 HAKMEM_TINY_TLS_LIST=0 HAKMEM_TINY_HOTMAG=0 \ -HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 \ -./larson_hakmem 2 8 128 1024 1 12345 4 - -./larson_hakmem 5 8 128 1024 1 12345 4 -``` - -デバッグ一発ログ(導通確認) -``` -HAKMEM_TINY_REFILL_OPT_DEBUG=1 \ -HAKMEM_TINY_TRACE_RING=0 HAKMEM_SAFE_FREE=0 \ -HAKMEM_TINY_TLS_SLL=1 HAKMEM_TINY_TLS_LIST=0 HAKMEM_TINY_HOTMAG=0 \ -HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 \ -./larson_hakmem 2 8 128 1024 1 12345 4 -``` - ---- - -## 🔧 箱化フェーズ(進行中) - -現状(2025-11-06 21:xx) - -- Phase 1 完了(安全・即効の分離) - - 退出ダンプ([EXIT DEBUG]/SS会計)を箱化 - - `core/box/hak_exit_debug.inc.h` を導入し、`hakmem.c` から関数本体を除去 - - KPIユーティリティ(/proc, RSS など)を箱化 - - `core/box/hak_kpi_util.inc.h` を導入し、`hak_get_kpi()` を移動 - - ビルド・2s/4T スモーク OK(Throughput ≈ 4.19M ops/s, 回帰なし) - -- Free/Tiny/SS 導通の暫定診断(短ランでOOMを避けつつ) - - 追加カウンタ: `g_free_wrapper_calls`, `g_hak_tiny_free_calls`, `g_free_ss_enter`, `g_free_local_box_calls`, `g_free_remote_box_calls`, `g_ss_active_dec_calls` - - 短ラン結果: free() は大量に呼ばれているが、Tiny/SS側の free 経路には一度も到達していない(全て 0) - - OOM傾向はこの導通不全が主因。長時間ランは回避し、短ランで追跡継続。 - -次にやること(Phase 2: 中核APIの箱化 + 診断の最小追加) - -1) hak_alloc_at / hak_free_at の箱化(見通し改善・安全に戻せる設計) - - 新規: `core/box/hak_alloc_api.inc.h`(alloc本体) - - 新規: `core/box/hak_free_api.inc.h`(free本体) - - `hakmem.c` から本体を除去し、1 行 include へ差し替え - - 目的: `hakmem.c` を 500行級へ圧縮し、中央ハブとしての見通しを確保 - -2) Free 導通の最小スケスケ(短ラン限定・ワンショット) - - ENV: `HAKMEM_FREE_ROUTE_TRACE=1` で最初の N 件だけ分類ログ - - `super(registry ok/miss)` / `mid(hit/miss)` / `l25(hit/miss)` / `unknown` - - OOMを避けるため 2s/4T のみで実行。Tiny/SSへ届かない原因を最短で特定。 - -3) Phase 3 の見積もり(必要時) - - init/shutdown の箱化(`core/box/hak_core_init.inc.h`) - - 最終目標: `hakmem.c` を中央 include ハブ(~400–600行)に固定 - -A/B・戻せる設計 - -- すべて `.inc.h` の差し替え(1行)で段階導入。問題が出たら即時リバート可。 - -タイムライン(目安) - -- Phase 2: 1–2時間(箱化) + 1時間(2s/4T 短ラン×数回) -- Phase 3: 1–2時間(箱化) + 30分(スモーク) - - -## 🧱 本日の着手(L2.5/L2 キャッシュ → ベンチ) - -- 目的: vm/mixed での大サイズ(≥512KB)の再利用性を引き上げ、mimalloc/system に肉薄/逆転。 -- 仕様(箱理論): - - BigCache-L25 ゲート(A/B) - - env `HAKMEM_BIGCACHE_L25=1` で、512KB–<2MB のサイズも per-site BigCache を利用。 - - 境界1箇所:alloc/free のみ(他経路には侵食しない)。 - - Fail-Fast と戻せる設計:env で即時OFF可。 -- 実装: - - `core/hakmem.c` に BigCache-L25 ヒット/プット分岐を追加(A/Bフラグで制御)。 - - 既存 BigCache(≥2MB)は維持しつつ、L2.5 も同一箱を使って簡素化。 -- ベンチ: - - ハーネス復元(`bench_allocators_{hakmem,system}`)。 - - 4シナリオ × 3アロケータ(system/mimalloc/HAKMEM)× 5回の自動化を強化し、CSV保存。 - - シナリオ案: random_mixed(16–1024B, 1T), mid_large_mt(8–32KiB, 4T), larson(8–128B, 4T), redis-like(16–1024B, 1T, LD_PRELOAD) - - 出力: `bench_results/auto//*.csv`(ops/s, 備考列にENV) - - クイックCSV(5×4シナリオ×3アロケータ)を `bench_results_archive/` に保存。 - -次アクション: -1. `HAKMEM_BIGCACHE_L25=1` で quick_full_benchmark を再実行し、vm/mixed の改善を確認。 -2. 改善が見られれば、THPゲートとdecommitバッチのA/Bを追加実装(箱と境界は現行踏襲)。 - -## 🎯 次の主目標(mimalloc 対決: Larson/TinyHot) - -1) Hot Tiny リフィル最適化(Box 4 境界の探索コスト縮減) -- [ ] `HAKMEM_TINY_REFILL_COUNT_HOT={48,64}` を A/B。L1d miss と IPC を `perf stat` で取得。 -- [ ] `tiny_refill_try_fast()` の class<=3 優先化(MailBox→Sticky/Hot の順)と早期 return の徹底。 -- [ ] Drain のチェイン splice(済)を維持しつつ、不要ドレインの抑制(remote_pending のみ)。 - -2) ベンチ・スナップショット -- [ ] `scripts/bench_capture_now.sh` で現構成を保存(日付入り)。 -- [ ] `scripts/profiles/tinyhot_best.env` に `REFILL_COUNT_HOT`/`FAST_CAP` を追記しベストを固定。 - -3) 比較と回帰防止 -- [ ] Larson 2s/4s × threads=1/4/8 で hakmem vs mimalloc を測定・記録。 -- [ ] Guard/ASan を OFF のまま長時間(>30s)を1回通して安定性を確認。 - -4) 伸びなければ(次の一手) -- [ ] class<=3 の探索順再編(Sticky/Hot/Bench の順入替)を小さく A/B。 -- [ ] それでも平坦なら、Front(最前段)の前倒しポップ(SLL 事前 pop)を A/B(Box 5 内だけで完結)。 - - -## 🔔 最新アップデート (2025-11-06 19:40) - -- RemoteTrack Box(debug-only)を導入し、ノードの状態遷移(alloc→remote→drain→freelist)を追跡。矛盾時は `REMOTE_TRACK_MISMATCH`+BT を出力。 -- SlabHandle を全採用経路に統一:所有権取得→`slab_drain_remote_full()`→ゼロ確認→採用失敗時は release。Box2/Box3 の境界を1箇所へ集約。 -- TLS alloc 前に remote queue を opportunistic drain(`superslab_alloc_from_slab`)し、side-table 上に残るノードは guard モードで Fail-Fast。 -- guard 走行では `REMOTE_TRACK_MISMATCH stage=alloc_ret` がまだ発生。原因は「remote queue が drain されず、採用境界をすり抜けて TLS 返却されたノードが存在」。 -- 次手:採用境界に“remote 残があれば採用しない”ゲートを追加し、配布直前に side-table を再確認(guard 時のみ)して強制停止する。 - -## 🔔 最新アップデート (2025-11-04 12:20) - -直近の構造改善(箱の積み直し v2) - -1) SlabHandle Box(core/slab_handle.h, ~100行) -- 所有権取得・リモートキュードレイン・メタデータアクセスをカプセル化 -- 型安全なハンドル(valid==1 のときのみ drain/modify が可能) -- Invariant: drain/push/pop は「所有権取得後のみ」実行可(境界1箇所)。 - -2) 6 箇所のリファクタリング(SlabHandle 適用) -- tiny_refill.h: Sticky / Hot / Bench / Mailbox 採用箇所(4箇所)で、候補決定→SlabHandle 取得→remote_drain→bind の順へ整理 -- tiny_mmap_gate.h: Registry scan の採用箇所(1箇所)を SlabHandle 化 -- hakmem_tiny_free.inc: SuperSlab adopt path(1箇所)を SlabHandle 化 - -3) 所有権なし drain のバグ修正 -- hakmem_tiny_superslab.h:376 の `ss_remote_drain_light()` が ownership 無しで drain していた点を是正 -- 修正: `ss_owner_try_acquire()` で取得に成功した場合のみ `ss_remote_drain_to_freelist()` を実行 - -現状の問題(継続中) -- 依然 crash(fault_addr=0x6261)。Tiny Debug Ring にて以下を観測: - - [N] event=free_enter class=0 ptr=0x...28c0 - - [N+1] event=free_enter class=3 ptr=0x...28c0 ← 同一ポインタを異なる class で 2 回 free -- 結論: freelist 破損ではなく、クラス判定ミス or 二重 free(UAF)の可能性が高い。 -- Hypothesis: - - hak_super_lookup() の再マップ(旧 SS → 新 SS 同一アドレスで別 class)が、二重 free を“別 class”に見せている。 - - 実態は上位レイヤ(呼び出し側)の二重 free である可能性が高い。 -- 対応(デバッグ方針): - - `HAKMEM_SAFE_FREE=1` を既定ON推奨(デバッグ期間)。 - - SS free 境界でブロック整合チェック(slab_base/offset/size/容量)と freelist 簡易スキャン(<=64)で二重 free を検出。 - - SS/Tiny の二重ルックアップ比較(両者が同時に見つかり class が不一致ならリングに記録)。 - - `HAKMEM_SAFE_FREE_STRICT=1` なら Fail‑Fast(SIGUSR2)で即座に停止。 - -最優先課題は「FAST_CAP=0(fast-tier OFF)時に 4T Larson で再現する SuperSlab remote free の SEGV」を潰すことです。publish→mail→adopt は通電が確認できており、先に Box 2/3(Remote/Ownership)を箱単位で健全化します。その後、L2.5/L2 BigCache のA/Bを本番ハーネスで収集(CSV)します。 - ---- - -## 🚀 後段つよつよ大作戦(mimalloc 撃破作戦) - -目標(Objective) -- 「Larson(8–128B)」と「mid/mixed」代表ワークロードで mimalloc を撃破 or 同等に接近する。 - - 直近ターゲット(10秒計測) - - Larson 4T: ≥ 12–14M ops/s(段階目標)、最終:mimalloc≒16.7M ops/s に接近 - - Mid/Large MT 4T: systemの80%→100% 到達 - - Random Mixed 1T: 2–3x 改善(PF/sys 抑制で底上げ) - -作戦(Box Theory に基づく後段強化) -- Adopt/Ready 優先箱(取り出しO(1)) - - Ready List(per-class slab hint)を最前段で採用。publish/remote/first-free で push、refill で pop→bind。 - - A/B: `HAKMEM_TINY_READY=1`、cooldown=0、scan窓縮小(REG_SCAN_MAX=64/32/16)。 -- Registry/探索の削減箱 - - per-class registry の窓幅をさらにチューニング(64→32→16)。first‑fit で即帰還。 - - `HAKMEM_TINY_REG_SCAN_MAX` をマトリクスで最適点探索。 -- Superslab/Mmap Gate(must‑adopt‑before‑mmap 強化) - - adopt×2(yield前後)+ Ready→Mailbox→Registry の順固定。mmap は最終手段。 - - Gate内で sticky を先行、必要に応じて small ドレイン(所有権必須)。 -- L2.5/L2 BigCache(VM寄り) - - L2.5(512KB–<2MB)も per‑site BigCache A/B(`HAKMEM_BIGCACHE_L25=1`)。 - - 狭帯域(512KB–1MB)シナリオでヒット率を上げ、PF/sys を可視に低減。 - -可視化/計測(スクリプト整備済み) -- CSV出力マトリクス(reps=5/10, 10秒ラン) - - Larson triad: `benchmarks/scripts/run_larson_matrix.sh 2,10 1,4 REPS` - - Mid/Large MT: `benchmarks/scripts/run_mid_large_mt_matrix.sh 1,4 CYCLES WS REPS` - - Random Mixed: `benchmarks/scripts/run_random_mixed_matrix.sh CYCLES WS REPS` - - VM Mixed(L2.5 A/B): `benchmarks/scripts/run_vm_mixed_matrix.sh CYCLES WS REPS` - - Redis-like(LD_PRELOAD): `benchmarks/scripts/run_redis_matrix.sh THREADS CYCLES OPS REPS` -- perf stat(PF/dTLB/IPC/branches)を10秒ランに併記(Larson/Mid中心)。 - -TODO(短期ロードマップ) -1) Larson 2s→10s(1T/4T)で REG_SCAN_MAX × READY × ADOPT のA/B(CSV+perf) -2) Mid/Large MT 10s(1T/4T)で採用窓とGate強化の最適点探索(CSV+perf) -3) VM Mixed 狭帯域(512KB–1MB)で L25=ON/OFF の差を定量(CSV) -4) Redis LD_PRELOAD 安定化(Tiny安全モード→段階的拡張) -5) ベスト設定を `benchmarks/RESULTS_SNAPSHOT.md` に反映、`benchmarks/README.md` に推奨ENV追記 - -リスク/フォールバック -- READY/ADOPT はA/Bガード付き(env)で即時切替可。Gate強化も1箇所の境界内で適用/解除する。 -- L2.5 BigCache は狭帯域で先行検証(広帯域ではオーバーヘッド優位になりやすい)。 - -### 症状(Larson 2s, 4T, FAST_CAP=0) -- `hak_tiny_free_superslab()` → `ss_remote_push()` → `tiny_publish_notify()` あたりで SIGSEGV。`fault_addr` は常に低い値(例: 0x6261)で、invalid ポインタ参照。 -- Debug Ring で直前イベントを記録すると、「class=4 の remote free → alloc → free_remote → alloc → …」が循環。ptr は SuperSlab 内に見えるが、キューに積まれた時点で破損疑い。 -- FAST_CAP>0 に戻すと crash は発生せず、throughput ≈3.7M ops/s(以前より低下)。publish pipeline がゼロのままのため本質的な性能改善は未着手。 - -### 箱構成と境界 -1. **FrontGate(fast-tier/Hot/TLS)の箱** - - `tiny_fast_pop/push`, `hotmag_pop/push`, TLS SLL/Magazine - - ★ 現在の仮説:FAST_CAP=0 で front が完全にバイパスされる際、remote queue への戻し順序が乱れている -2. **RemoteQueue(ss_remote_push/dain)の箱** - - SuperSlab remote_heads / remote_counts / slab_listed - - `tiny_publish_notify` が通知境界 -3. **Mailbox/Publish の箱** - - `tiny_mailbox_publish/fetch`, `ss_partial_publish/adopt` -4. **Debug Ring(可視化の箱)** - - `HAKMEM_TINY_TRACE_RING=1` で alloc/free/publish 直前イベントをダンプ - -現在の crash は FrontGate または RemoteQueue の内部バグが境界越えで露呈している状態 → 境界を固めて中/外どちらに異常があるか見極める。 - -### 進行中のタスク -1. **RemoteQueue 安全化(優先)** - - [ ] `ss_remote_push()` にポインタ境界チェック(スーパースラブ内か)と Debug Ring ログ追加 - - [ ] remote push 後に `tiny_publish_notify` へ渡すフォーマット(ss+slab_idx)を `tiny_mailbox_publish` で検証用トレース - - [ ] `ss_remote_drain_to_freelist` 前後で freelist pointer をトレースし、破損発生位置を特定(remote queue 内滞留→再 push の経路切り分け) - - [x] `tiny_remote_sentinel_set()` で 0x6261 汚染を一度だけ捕捉し BT + SIGUSR2 を吐くトラップを追加(再現経路の特定用) - - [x] remote side table を箱化 (`1<<20` + `tiny_remote_side_clear()` + `side_overflow` フォールバック) し、飽和時でも sentinel/next の整合を維持 - - [ ] sentinel 汚染が発生した際の callstack を段階別(set/scan/drain)に収集し、同一ノードへの二重 push(実質 double free)発生箇所を pinpoint -2. **FrontGate バイパス検証** - - [ ] `FAST_CAP=0` 時専用のトレースフラグ `HAKMEM_TINY_DEBUG_FAST0=1` で front 層を最小順序に固定(Hot/TLS/Mag を飛ばして直接 remote → TLS リストへ流す) - - [ ] フラグ ON/OFF で crash が消えるか確認 → front 内バグか remote 内バグかを切り分け -3. **再現テスト・フラグ** - - [ ] `scripts/run_larson_defaults.sh tput` に `HAKMEM_TINY_FAST_CAP` オーバーライドを追加(忘れ防止) - - [ ] `scripts/` に crash 再現スクリプト `run_larson_fast0.sh` を用意(2s/4T で SIGSEGV を取得) -4. **publish pipeline 調査(二次優先)** - - crash 解消後に `tiny_publish_notify` の発火率と mailbox drain を再計測し、mmap 偏重の根本原因へ戻る - -### 参考コマンド -- Crash 再現: - `HAKMEM_TINY_FAST_CAP=0 HAKMEM_LARSON_TINY_ONLY=1 HAKMEM_TINY_USE_SUPERSLAB=1 ./larson_hakmem 2 8 128 1024 1 12345 4` -- Debug Ring ダンプ: - `HAKMEM_TINY_TRACE_RING=1 ... ./larson_hakmem ...` - `kill -USR2 ` で途中ダンプ、`SIGSEGV` で最終ダンプ -- publish 通電検証(安全ラン): - `scripts/run_larson_defaults.sh tput 2 4` - - -現状整理(Google系/Larson系ベンチの追い上げフェーズ) - -### 実施済み(即応) -- ベンチ実行時のデバッグ出力によるオーバーヘッドを除去(リリース既定で抑制) - - 変更ファイル: `core/hakmem_tiny_ultra_simple.inc`, `core/hakmem_tiny_metadata.inc` - - 方針: `HAKMEM_DEBUG_VERBOSE` が有効時のみ `fprintf` するようガード - - 効果: ログ出力がボトルネックになるケースを解消(特に tiny/mixed ベンチ) - -- `bench_random_mixed_hakmem` を ULTRA_SIMPLE 版で再ビルド・再計測 - - ビルド: `make bench_random_mixed_hakmem EXTRA_CFLAGS="-DHAKMEM_TINY_PHASE6_ULTRA_SIMPLE=1 -DHAKMEM_BUILD_RELEASE=1"` - - 旧: 23.49 M ops/sec → 新: 25.82 M ops/sec(+9.9%) - -- Larson ベンチ(2秒, 8–128B, chunks=1024, rounds=1, seed=12345)実行 - - system 1T: 14.73 M/s, 4T: 16.76 M/s - - mimalloc 1T: 16.77 M/s, 4T: 16.77 M/s - - HAKMEM 1T: 2.52 M/s, 4T: 4.19 M/s - - `HAKMEM_LARSON_TINY_ONLY=1 HAKMEM_DISABLE_BATCH=1` でも同等(~2.56M / ~4.19M) - -### 観測と仮説(Larson 遅さの主因) -- 既知の分析と一致: 再利用不足 → ページフォールト/ゼロ化増 → sys 時間が支配的 -- Tiny フロントのヒット率が不足(SLL を使うが、Larson パターンで十分に温まらない) -- Metadata 版(Phase 6-1.6)は refill 未対応部分があり現状は封印、ULTRA_SIMPLE で進めるのが安全 - -### 当面の方針(追いつくまでの短期プラン) -1) Larson 用バイナリに ULTRA_SIMPLE を徹底適用してフロントのヒット率を最大化 - - 目標: free/alloc ともに 3–4 命令の経路に乗せる(既に `free()` は alignment-guess 経路有効) - - ビルド: `EXTRA_CFLAGS="-DHAKMEM_TINY_PHASE6_ULTRA_SIMPLE=1 -DHAKMEM_BUILD_RELEASE=1"` - -2) Mixed/Larson の再測定と perf 取得 - - コマンド: `scripts/run_larson.sh -d 2 -t 1,4` - - 詳細: `scripts/run_larson_perf.sh`(PF/IPC/branch/L1d を併記) - -3) 迅速チューニング候補(小粒で効く順) - - Refill 個数の抑制(64→16〜32)で温まり時間短縮、TL-再利用密度を上げ PF を減らす - - SuperSlab サイズの下限を 1MB に固定(`HAKMEM_TINY_SS_MIN_MB=1`)で初期PFを軽減 - - 事前ウォーム(Larson開始前に `sll_refill_small_from_ss()` をサイズ帯毎に数回) - - size→class 変換の LUT 確認(既に O(1) だが、統合経路でも分岐予測を安定化) - -4) 中期(必要なら) - - Dual Free Lists(local/remote 分離)を Tiny に導入(既存の設計を Tiny へ移植) - - Metadata 版の refill 実装を完了(ヘッダ +8B で owner 判定ゼロ化)し安定化後に切替検討 - -### 直近 TODO(実行順) -- [ ] Larson ULTRA_SIMPLE ビルドの固定化(larson_hakmem に EXTRA_CFLAGS 反映) -- [ ] `scripts/run_larson.sh -d 2 -t 1,4` の再実行と結果更新 -- [ ] `scripts/run_larson_perf.sh` で PF/CPU 内訳を取得してボトルネックの再確認 -- [ ] Refill 個数/SSサイズのチューニングで 1T: ~10M, 4T: ~10M の域まで引き上げ - -備考: この更新で、測定時のノイズ(fprintf)は排除済み。以降の差分は純粋にアルゴリズム/チューニング起因として評価可能。 - ---- - -## 🔧 バグ修正と3点セット実装(2025-11-03 09:00) - -結論: 「free 経路の破綻」は修正済み。OOM は設計的な再利用探索の不足が残課題。 - -- 修正 - - ULTRA_SIMPLE free を same-thread のみ直 push に制限。cross-thread free は従来経路へフォールバック。 - - 変更: `core/hakmem.c:820`, `core/hakmem_tiny_ultra_simple.inc:96` - - OOM ワンショット診断(errno/ss_size/alloc_size/RLIMIT/VmSize/RSS/SSカウンタ)を追加。 - - 変更: `core/hakmem_tiny_superslab.c:182` - -- 3点セット(段階導入・既定OFF) - 1) remote queue(cross-thread free 時に per-slab MPSC stack へ) - 2) partial publish/adopt(クラス別公開リング→refill 前に adopt) - 3) adopt 時の remote drain + owner 移譲(best-effort) - - 変更: `core/hakmem_tiny_superslab.h`, `core/hakmem_tiny.c`, `core/hakmem_tiny_free.inc` - - 有効化: `HAKMEM_TINY_SS_ADOPT=1` - -- 観測(Larson, 4T) - - adopt OFF(既定): ~4.19 M/s 安定、稀に ENOMEM 継続 - - adopt ON: OOM は減るがゼロにはならず。1T は低下傾向(~2.3–2.4 M/s)→チューニング要 - -- 次のチューニング(提案) - - `SS_PARTIAL_RING` 長(2/4/8)A/B、adopt 選好(remote多い slab 優先)、採用頻度の抑制(クールダウン) - - perf stat(PF/DTLB)の比較で改善度を定量化 - -使い方(A/B): -``` -# 既定(adopt OFF) -./larson_hakmem 2 8 128 1024 1 12345 4 - -# adopt ON(3点セット有効、A/B計測) -HAKMEM_TINY_SS_ADOPT=1 ./larson_hakmem 2 8 128 1024 1 12345 4 -``` - - -## 🎯 現在のミッション: Phase 6 - Learning-Based Tiny Allocator - -**Status**: ✅ **Phase 6-1 完了!** 🚀🎉 - -**最新アップデート (2025-11-02 18:00):** -- ✅ **Phase 6-1: Ultra-Simple Fast Path 完了!** 🚀🚀🚀 - - **驚異的な結果**: **478.60 M ops/sec** (64B LIFO) - - **System malloc の +174% 高速!** (174.69 M/s → 478.60 M/s) - - **現行 HAKMEM の +777% 高速!** (54.56 M/s → 478.60 M/s) - - **4.17 cycles/op** (理論的最小値に近い) - - **100% hit rate** (10M ops 中 miss 1回のみ) - - - **実装**: "Simple Front + Smart Back" (HAKX Mid-Large +171% の成功パターン適用) - - Fast path: 3-4 命令 (tcache風 pop from free list) - - Backend: Simple mmap-based chunk allocator - - Files: `core/hakmem_tiny_simple.{h,c}` (200行) - - - **なぜこんなに速い?** - 1. Ultra-simple fast path (分岐予測完璧) - 2. Perfect L1 cache locality (TLS array 64B) - 3. Freed blocks 即再利用 (LIFO で 100% hit) - 4. ゼロオーバーヘッド (magazine layers なし) - - - **次のステップ**: - - [ ] Comprehensive benchmark (21 patterns) - - [ ] Memory efficiency 測定 - - [ ] Phase 2: Learning layer 設計 - ---- - -## 📋 過去の試行 (Phase 5以前) - -**Status (旧)**: ✅ Phase 2+1完了 → ❌ Phase 3失敗 → ❌ Phase 4-A1失敗 → ❌ **Phase 5-A失敗** → ❌ **Phase 5-B-Simple 失敗** 💥 - -**最新アップデート (2025-11-02 07:00):** -- ❌ **Phase 5-B-Simple 失敗**: **-71% (ST) / -35% (MT)** 💥💥💥 - - **Single-threaded (bench_tiny_hot, 64B)**: - - System: 169.49 M ops/sec - - HAKMEM Phase 5-B: 49.91 M ops/sec - - **Regression: -71%** (3.4x slower!) - - - **Multi-threaded (bench_mid_large_mt, 8-32KB, 2 threads)**: - - System: 11.51 M ops/sec - - HAKMEM Phase 5-B: 7.44 M ops/sec - - **Regression: -35%** (1.5x slower) - - ⚠️ **NOTE**: Mid/large benchmark tests 8-32KB allocations (outside Tiny range), not directly testing Phase 5-B - - - **根本原因分析**: - 1. **Magazine capacity ミスチューン**: 64 slots は ST workload には小さすぎる - - Batch=100 の場合、2回に1回は slow path に落ちる - - System allocator は tcache (7+ entries per size) で高速 - - 2. **Migration logic オーバーヘッド**: Slow path での free list → Magazine migration が高コスト - - Batch migration (32 items) が頻繁に発生 - - Pointer chase + atomic operations - - 3. **Dual Free Lists の誤算**: ST では効果ゼロ、むしろオーバーヘッド - - ST では remote_free は発生しない - - Dual structures のメモリ overhead のみ - - 4. **Unified Magazine の問題**: 統合で simplicity は得たが performance は失った - - 旧 HotMag (128 slots) + Fast + Quick の組み合わせのほうが高速だった - - 単純化 ≠ 高速化 - - - **教訓**: - - ✅ **Magazine unification 自体は良アイデア** (complexity 削減) - - ❌ **Capacity tuning が不適切** (64 slots → 128+ 必要) - - ❌ **Dual Free Lists は MT 専用** (ST で導入すべきでない) - - ❌ **Migration logic が重すぎる** (batch size 削減 or lazy migration 必要) - - - **次のアクション**: - 1. ⏮️ **Phase 5-B をロールバック** (git revert) - 2. 📊 **Baseline 再測定** (clean state で確認) - 3. 🤔 **Phase 5-B-v2 を検討** (Magazine unification only, Dual Free Lists なし) - 4. 🎯 **Alternative approach**: Phase 6 系 (L25/SuperSlab 最適化) に移行 - -- 🚀 **Phase 5-B-Simple 開始** (2025-11-03 04:00): Dual Free Lists + Magazine統合 🎯 - - **Phase 5-A-v2 をスキップする理由**: - 1. **HAKMEM は既に O(1) size→class 変換** (`g_size_to_class_lut_1k[size]`) - 2. **多層 cache が既に 95%+ hit** → direct cache 追加は効果薄い - 3. **TLS 変数追加で cache pollution** リスク - 4. **期待値**: ±0% (効果とオーバーヘッドが相殺) - - **Phase 5-B-Simple に集中** (期待: +15-23%) - - - **2つの最適化を統合**: - 1. **Dual Free Lists** (mimalloc Phase 5-B): +10-15% - - Local free で atomic 不要 (10+ cycles 削減) - - Cache locality 向上 - 2. **Magazine 統合** (シンプル化): +3-5% - - 3-4層 → 2層に削減 - - TLS cache line を 3-4本 → 1-2本に削減 - - Branch を 3-4回 → 1回に削減 - - - **実装計画** (3-4 days): - ```c - // === Before (現状: 複雑) === - // Layer 1: HotMag (classes 0-2) - if (g_hotmag_enable) ptr = hotmag_pop(); - - // Layer 2: Hot functions (classes 0-3) - if (g_hot_alloc_fn[cls]) ptr = tiny_hot_pop_classN(); - - // Layer 3: Fast cache - ptr = tiny_fast_pop(cls); - - // Layer 4+: Slow path (single freelist + atomic) - ptr = hak_tiny_alloc_slow(); - - // === After (Phase 5-B-Simple: シンプル) === - // Layer 1: Unified Magazine (統合版) - TinyUnifiedMag* mag = &g_tls_mag[cls]; - if (mag->top > 0) return mag->items[--mag->top]; // ← 1 branch! - - // Layer 2: Slow path (Dual Free Lists) - return tiny_alloc_slow_dual(cls); // ← local_free (no atomic!) - ``` - - - **Dual Free Lists の核心**: - ```c - typedef struct TinySlab { - // Phase 5-B: Dual Free Lists - void* local_free; // Local frees (no atomic!) - _Atomic(void*) thread_free; // Remote frees (atomic) - } TinySlab; - - // Free: Local は atomic なし! - if (pthread_equal(slab->owner_tid, self)) { - *(void**)ptr = slab->local_free; - slab->local_free = ptr; // ← No atomic! 10+ cycles 削減 - } else { - atomic_push(&slab->thread_free, ptr); - } - - // Migration: Batch で効率化 - if (!slab->free && slab->local_free) { - slab->free = slab->local_free; // Pointer swap only - slab->local_free = NULL; - } - ``` - - - **期待効果まとめ**: - | 最適化 | 期待効果 | 累積 M ops/sec | - |--------|---------|----------------| - | Baseline | - | 16.53 | - | Dual Free Lists | +10-15% | 18.2-19.0 | - | Magazine 統合 | +3-5% | 18.7-20.0 | - | Branch 削減 | +2-3% | 19.1-20.6 | - | **合計** | **+15-23%** | **19.1-20.3 M ops/sec** 🎯 | - - - **実装ステップ**: - - **Step 1** (Day 1): Unified Magazine 実装 & benchmark - - **Step 2** (Day 2): Dual Free Lists 追加 (TinySlab 構造変更) - - **Step 3** (Day 3): Free path 書き換え (local_free / thread_free 分離) - - **Step 4** (Day 4): Migration logic & 最終 benchmark - - - **削減される TLS 変数**: - ```c - // Before (~1600 bytes + flags) - __thread TinyHotMag g_tls_hot_mag[8]; // 1024 bytes - __thread void* g_fast_head[8]; // 64 bytes - __thread uint16_t g_fast_count[8]; // 16 bytes - __thread TinyQuickSlot g_tls_quick[8]; // 512 bytes - - // After (2048 bytes のみ) - __thread TinyUnifiedMag g_tls_mag[8]; // 2048 bytes - ``` -- ❌ **Phase 5-A失敗**: **-3~-7.7%** (16.53 → 15.25-16.04 M ops/sec) 💥 - - **実装**: Global `slabs_direct[129]` でO(1) direct page cache - - **結果**: 性能**大幅悪化**(期待+15-20% → 実際-3~-7.7%) - - **根本原因**: **Thread-local vs Global の設計ミス** - ```c - // hakmem_tiny.h - 間違った実装 - typedef struct { - TinySlab* slabs_direct[129]; // ❌ Global = 全threadが共有 - } TinyPool; // g_tiny_pool は global - - // mimalloc - 正しい実装 - typedef struct mi_heap_s { - mi_page_t* pages_free_direct[129]; // ✅ Heap ごと = thread-local - } mi_heap_t; // __thread mi_heap_t* heap - ``` - - **性能悪化の3つの要因**: - 1. **pthread_self() オーバーヘッド**: 毎回 `tiny_self_pt()` syscall (~20-40 cycles) - 2. **Remote slab hit**: Global cache が他 thread の slab を指す - → Owner check → Cache clear → Fallback (無駄な3段階処理) - 3. **余分な分岐**: 既存 fast path の前に新たな条件分岐層を追加 - - **測定結果** (2回計測): - - Run 1: 15.25 M ops/sec (-7.7%) - - Run 2: 16.04 M ops/sec (-3.0%) - - Baseline: 16.53 M ops/sec - - **教訓**: **mimalloc の直訳は危険** - アーキテクチャ差異を理解すべき - - mimalloc: `mi_heap_t` は thread-local → `pages_free_direct` も thread-local - - HAKMEM: `TinyPool g_tiny_pool` は global → `slabs_direct` は全 thread 共有 - - **Revert**: 全ての Phase 5-A 変更を完全に revert (16.33 M ops/sec に復元) - - **次の戦略**: Phase 5-A-v2 で **thread-local slabs_direct** を実装 - ```c - // 次回の正しい実装方針 - __thread TinySlab* g_tls_slabs_direct[129]; // ✅ Thread-local - ``` -- 🎊 **Phase 5完了: 47% Gap の正体を解明!** 🔍 - - **mimalloc 完全分析**: 10,000語超の詳細レポート作成完了 - - **3つのファイル生成**: - - `MIMALLOC_ANALYSIS_REPORT.md` - 詳細技術分析 - - `MIMALLOC_KEY_FINDINGS.md` - 要約版 - - `MIMALLOC_IMPLEMENTATION_ROADMAP.md` - 実装計画 - - **Gap の内訳判明**: - 1. **Direct Page Cache (O(1))**: +15-20% ← **最大のボトルネック!** - 2. **Dual Free Lists**: +10-15% - 3. **Branch Hints + Lazy Updates**: +5-8% - - **重要な発見**: mimalloc も **intrusive linked list** を使用 - → Phase 3/4-A1 の「linked-list は最適」は**正しかった**! - → Gap は**マイクロ最適化**から来る(データ構造選択ではない) - - **Hot Path サイクル比較**: - - mimalloc: ~20 cycles (TLS 2 + O(1) lookup 3 + Pop 5) - - HAKMEM: ~30-35 cycles (TLS 3 + Binary search 5 + Atomic pop 15) - - **差分: 10-15 cycles → 47% の性能差** -- 📋 **次のアクション**: Phase 5-A 実装(Direct Page Cache) - - **期待効果**: +15-20% (16.53 → 19.0-19.8 M ops/sec) - - **Effort**: 1-2 days - - **Risk**: Low -- ❌ **Phase 4-A1失敗**: **-0.24%** (16.53 → 16.49 M ops/sec) 💥 - - 実装: TLS-BUMP即値化(immediate-value hot functions) - - 結果: 性能**悪化**(期待+5-8% → 実際-0.24%) - - 原因: **TLS-BUMPは mixed workloadで機能しない** - ```c - // hakmem_tiny_refill.inc.h:258 - if (meta->freelist != NULL) return NULL; // linear mode only - ``` - - 根本問題: bench_random_mixed (50% alloc, 50% free) では freelist が常に populated - - **TLS-BUMPは monotonic allocationのみ有効**(連続allocのみ) - - 教訓: **混在ワークロードでは linked-list が最適** - 追加層は純粋なオーバーヘッド -- 🚀 **Phase 4戦略決定** (2025-11-02 22:00) - - ChatGPT Pro相談完了:構造的アプローチで+10-25%を狙う - - 核心: 現在の6-7層を**3層に統合**(層あたり2-3ns削減) - - 即効施策: TLS-BUMP即値化(+5-8%期待) ← **失敗!** - - 中期施策: 小マガジン128化(+3-5%)+ 3層リファクタ(+10-15%) - - 長期施策: Mid/Large構造改革(per-core arena + TL-Segment) -- ❌ **Phase 3失敗教訓**: **+0.24% のみ** (16.53 → 16.57 M ops/sec) - - 実装: v3 allocator (magazine-based single-tier) - - 原因: 既存のlinked-listが既にmagazineより最適 - - 教訓: **Linked list > Magazine array**(メモリアクセス少ない) -- ✅ **Phase 2+1完了**: **+1.8% 改善** (16.24 → 16.53 M ops/sec) - - Phase 2: TLS range check実装 (owner_slab高速化) - - Phase 1: free()順序変更 (Tiny → Mid MT) - - 結果: 理論通り動作、軽微な改善 ✅ -- 📋 **次のアクション**: Phase 4-A1実装開始(TLS-BUMP即値化) - -**Phase 2+1の教訓:** -1. ✅ TLS range check + 順序変更は**理論通り動作**(50% overhead削減) -2. ❌ free() overheadが想定より小さかった(実測 ~8% vs 想定 ~21%) -3. 💡 **シングルスレッドでは mutex overhead は誤差レベル** -4. 🎯 さらなる改善には**malloc/alloc側**を攻めるべき(27% overhead!) - -**コスト分析(Phase 2+1):** -``` -Before (Mid → Tiny): - Mid null checks: 4 cycles - Mid mutex (empty): 15 cycles ← 想定より軽い! - Tiny registry: 15 cycles - Total: 34 cycles - -After (Tiny → Mid): - TLS check: 2 cycles - Tiny registry: 15 cycles - Total: 17 cycles - -Savings: 17 cycles (50% 削減) -全体への影響: 17 cycles × (free overhead 8%) ≈ +2% 🎯 -``` - ---- - -## 📊 最新perf分析結果 (2025-11-01) - -### ベンチマーク条件 -- **ワークロード**: bench_random_mixed (8-128B, 16 size classes) -- **パラメータ**: 200K cycles, 400 ws, seed=1 -- **スレッド**: 1 (シングルスレッド) - -### パフォーマンス比較 -| Allocator | Throughput | vs mimalloc | -|-----------|------------|-------------| -| HAKMEM | 16.46 M ops/sec | 68% | -| mimalloc | 24.21 M ops/sec | 100% | - -**Gap: 32% slower** ⚠️ - -### 根本原因: アロケータオーバーヘッド - -| Allocator | Total Overhead | malloc/alloc | free/delete | -|-----------|----------------|--------------|-------------| -| mimalloc | 17% | 7.35% | 9.77% | -| HAKMEM | **49%** | 27% | 21.64% | - -**HAKMEM は 3x のCPUサイクルを消費!** - ---- - -## 🔴 Critical Bottlenecks (優先度順) - -### 1. `free()` の無駄なMid Range チェック (Priority 1) 🔥 - -**問題**: -``` -free(ptr) - ↓ -Lock g_mid_registry mutex (2.29%) ← 全freeでロック! - ↓ -Binary search g_mid_registry (7.08%) ← 8KB-32KB範囲チェック - ↓ -Unlock mutex (3.93%) - ↓ -hak_tiny_owner_slab (4.98%) ← 8B-1KB範囲チェック (本来これだけでOK!) - ↓ -hak_tiny_free ← 実際のfree -``` - -**無駄**: 8-128Bのワークロードなのに、全freeでMid Range (8KB-32KB) をチェック! -- mutex lock/unlock: 6.22% -- mid_lookup: 7.08% -- **合計 13.3% のオーバーヘッド** が不要 - -**修正方針**: -```c -free(ptr) - ↓ -Fast TLS range check (Tiny: 8B-1KB) ← NO MUTEX, 直接チェック - ↓ (hit: ~90% for this workload) - hak_tiny_free - ↓ (miss) -Check Mid registry (with mutex) - ↓ (miss) -Check L25/other -``` - -**期待効果**: **~13% スループット向上** (16.46 → 18.6 M ops/sec) - ---- - -### 2. `hak_tiny_owner_slab` の線形探索 (Priority 2) 🟡 - -**問題**: -- 現在: TLS slab listを線形探索 (3.50% overhead) -- mimalloc: ポインタビットパターンで高速判定 - -**修正方針** (mimalloc-style): -```c -// 1. Alignment check -if ((ptr & (SLAB_SIZE - 1)) != 0) return NULL; // Not slab-aligned - -// 2. TLS range check -if (ptr < tls_heap_start || ptr >= tls_heap_end) return NULL; - -// 3. Direct slab header access -SlabHeader* slab = (SlabHeader*)(ptr & ~(SLAB_SIZE - 1)); -return slab; -``` - -**期待効果**: **~3% スループット向上** (3.50% → ~0.5%) - ---- - -### 3. `hak_tiny_alloc_slow` の複雑なフォールバック (Priority 3) 🟢 - -**問題**: -- 4段階フォールバック: hotmag → TLS list → superslab → magazine -- 各段階でTLS変数アクセス + 分岐 - -**修正方針**: -```c -// 1. TLS magazine (most common) -if (fast cache has space) return pop(); - -// 2. Superslab (if enabled) -if (g_use_superslab && superslab_active) return superslab_alloc(); - -// 3. Central refill -return refill_from_central(); -``` - -**期待効果**: **~2-3% スループット向上** - ---- - -### 4. 関数インライン化 (Priority 4) 🟢 - -**候補**: -- `hak_tiny_alloc` → `malloc` にインライン -- `hak_tiny_owner_slab` → `free` にインライン - -**期待効果**: **~2% スループット向上** - ---- - -## 🎨 Phase 2詳細設計: mimalloc-style Fast Owner Check - -### 現在の問題点 - -**現在の `hak_tiny_owner_slab()` 実装:** -```c -TinySlab* hak_tiny_owner_slab(void* ptr) { - int hash = registry_hash(slab_base); // 関数呼び出し + hash計算 - for (int i = 0; i < SLAB_REGISTRY_MAX_PROBE; i++) // 最大8回ループ - // array access + 比較 + atomic load - if (ptr < start || ptr >= end) return NULL; // range validation -} -``` - -**コスト分析:** -- **Positive lookup** (Tiny allocation): hash + 1-2 probes + range check = **~10-15 cycles** -- **Negative lookup** (非Tiny): hash + 1-2 probes = **~8-10 cycles** ← Phase 1失敗の原因! - -### Phase 2新設計: Ultra-Fast Owner Check - -**目標:** -- **Negative lookup**: **1-2 cycles** (現状: 8-10 cycles) → **~85% 削減** 🎯 -- **Positive lookup**: **5-8 cycles** (現状: 10-15 cycles) → **~40% 削減** - -**実装戦略:** -```c -// Phase 2: Ultra-fast owner check (mimalloc-style) -static inline TinySlab* hak_tiny_owner_slab_fast(void* ptr) { - // Step 1: TLS heap range check (1-2 cycles) ← KEY OPTIMIZATION - // Check if ptr is in this thread's Tiny heap range - if (ptr < g_tls_tiny_min || ptr >= g_tls_tiny_max) { - return NULL; // Outside TLS range → FAST NEGATIVE LOOKUP! ✅ - } - - // Step 2: Slab base calculation (1 cycle) - uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1); - - // Step 3: Registry lookup (2-3 cycles) - // Now only called for pointers IN TLS range (hit rate ~90%) - TinySlab* slab = registry_lookup(slab_base); - if (!slab) return NULL; - - // Step 4: Range validation (1-2 cycles) - if (ptr < slab->base || ptr >= slab->base + TINY_SLAB_SIZE) { - return NULL; - } - - return slab; -} -``` - -**最適化ポイント:** -1. **TLS range check を最初に実行** → Negative lookup を 1-2 cycles に! -2. Registry lookup は TLS range内のみ実行 → Hit rate ~90% -3. 既存のregistry_lookupを活用 → 安全性維持 - -### 実装要件 - -**1. TLS heap range tracking:** -```c -// hakmem_tiny.h -extern __thread void* g_tls_tiny_min; -extern __thread void* g_tls_tiny_max; - -// hakmem_tiny.c -__thread void* g_tls_tiny_min = (void*)UINTPTR_MAX; -__thread void* g_tls_tiny_max = NULL; - -// Update on slab allocation (in allocate_new_slab) -static inline void update_tls_tiny_range(void* slab_base) { - if (slab_base < g_tls_tiny_min) g_tls_tiny_min = slab_base; - void* slab_end = slab_base + TINY_SLAB_SIZE; - if (slab_end > g_tls_tiny_max) g_tls_tiny_max = slab_end; -} -``` - -**2. 既存コードとの互換性:** -- `hak_tiny_owner_slab()` をfast版に置き換え -- Registry lookup機構はそのまま維持 (thread-safe) -- SuperSlabは別途処理(既存通り) - -### 期待効果 - -**Negative lookup高速化 (非Tiny allocations):** -- Before: hash (2-3 cycles) + probe (3-4 cycles) + compare = 8-10 cycles -- After: range check (1-2 cycles) = **~85% 削減** 🔥 - -**Positive lookup高速化 (Tiny allocations):** -- Before: hash + probe + range = 10-15 cycles -- After: range + registry + range = 5-8 cycles = **~40% 削減** - -**Combined with Phase 1 (順序変更):** -``` -Tiny allocations (90%): - Before: Mid mutex (13.6%) + owner_slab (8-10 cycles) - After: owner_slab_fast (1-2 cycles) → Tiny free - Savings: 13.6% × 0.9 = ~12% 🎯 - -Mid MT allocations (10%): - Before: owner_slab (8-10 cycles) + Mid mutex - After: owner_slab_fast (1-2 cycles) + Mid mutex - Savings: 8 cycles × 0.1 = ~0.5% - -Total expected gain: ~12-13% (16.46 → 18.4-18.6 M ops/sec) -``` - ---- - -## 📋 実装タスクリスト - -### ✅ Phase 1: free() チェック順序変更 - **完了 (revert含む)** ✅ - -**実装完了:** -- ✅ 1-1. `hakmem.c` free()のチェック順序を変更 (Tiny→Mid) -- ✅ 1-2. ベンチマーク測定: 16.34 M ops/sec (**-0.73% regression**) -- ✅ 1-3. perf再測定して原因分析: `hak_tiny_owner_slab()` が重い (8-10 cycles) -- ✅ 1-4. Phase 1を revert: 16.24 M ops/sec (baseline復帰) - -**教訓:** `hak_tiny_owner_slab()` が重すぎて全freeに適用不可 → Phase 2で根本解決 - ---- - -### 🚀 Phase 2: owner_slab 高速化 - **実装中** 🚀 - -**目標:** -- Negative lookup: 8-10 cycles → **1-2 cycles** (~85%削減) -- Positive lookup: 10-15 cycles → **5-8 cycles** (~40%削減) -- Phase 2完了後、Phase 1再適用 → **~12-13%向上** - -**タスクリスト:** - -- [ ] 2-1. TLS range tracking変数追加 - - [ ] `hakmem_tiny.h`: extern宣言追加 - - [ ] `hakmem_tiny.c`: TLS変数定義 - - [ ] `allocate_new_slab()`: range更新ロジック追加 - -- [ ] 2-2. `hak_tiny_owner_slab_fast()` 実装 - - [ ] TLS range check (negative lookup高速化) - - [ ] Registry lookup (既存機構活用) - - [ ] Range validation (safety確保) - -- [ ] 2-3. 既存 `hak_tiny_owner_slab()` を置き換え - - [ ] 関数名変更 or 実装差し替え - - [ ] 全呼び出し箇所で動作確認 - -- [ ] 2-4. ベンチマーク測定 - - [ ] `./bench_random_mixed_hakmem 200000 400 1` - - [ ] 目標: ~16.24 M ops/sec (変化なし or 微増) - - [ ] 理由: owner_slab単体では効果小、Phase 1再適用で効果発揮 - -- [ ] 2-5. Phase 1再適用 (順序変更) - - [ ] `hakmem.c` free(): Tiny → Mid に変更 - - [ ] ベンチマーク測定 - - [ ] 目標: **18.4-18.6 M ops/sec (+12-13%)** 🎯 - -### 🎯 Phase 3: Allocation Hot Path簡素化 - **設計完了** 📐 - -**Status**: Phase 2+1完了 → Phase 3設計完了 (2025-11-01 22:00) - -**目標**: malloc/allocのoverhead削減 (27% → 20%) -- **期待効果**: +5-10% (16.53 → 17.5-18.0 M ops/sec) -- **アプローチ**: mimalloc-style single-tier hot path - -**現状分析 (perf):** -``` -malloc/alloc overhead: 27% - - hak_tiny_alloc_slow: 9.33% (複雑なフォールバック) - - hak_tiny_alloc: 7.12% (hot path) - - malloc wrapper: 3.67% - - その他: ~7% - -問題点: - ❌ 6+段階のフォールバックチェーン - ❌ 多数のTLS変数アクセス - ❌ Heavy stack frame (14.05% in prologue!) - ❌ Branch misprediction -``` - -**現在のフローチャート:** -```c -hak_tiny_alloc(size) - ↓ -1. size → class_idx - ↓ -2. ifdef BENCH_FASTPATH: - - SLL head check - - TLS Magazine check - - SLL refill - ↓ -3. HotMag front (class <= 2) - ↓ -4. Hot alloc functions (class 0-3) - ↓ -5. tiny_fast_pop() - ↓ -6. hak_tiny_alloc_slow() ← 9.33% overhead! - - HotMag refill - - TLS list refill - - SuperSlab fallback -``` - -**Phase 3新設計 (mimalloc-style):** -```c -void* hak_tiny_alloc_v3(size_t size) { - // 1. Size → class (branchless) - int class_idx = hak_tiny_size_to_class(size); - if (__builtin_expect(class_idx < 0, 0)) return NULL; - - // 2. Single-tier TLS magazine (HOT PATH - 2-3 cycles) - TinyTLSMag* mag = &g_tls_mags[class_idx]; - int top = mag->top; - if (__builtin_expect(top > 0, 1)) { - void* ptr = mag->items[--top].ptr; - mag->top = top; - return ptr; // ← 最速パス! 🚀 - } - - // 3. Refill + fallback (cold path) - return hak_tiny_alloc_slow_v3(size, class_idx); -} - -static void* __attribute__((cold, noinline)) -hak_tiny_alloc_slow_v3(size_t size, int class_idx) { - TinyTLSMag* mag = &g_tls_mags[class_idx]; - - // Step 1: Try refilling magazine from SuperSlab - if (mag_refill_from_superslab(class_idx, mag, 32) > 0) { - return mag->items[--mag->top].ptr; - } - - // Step 2: Allocate new SuperSlab - return hak_tiny_alloc_superslab(class_idx); -} -``` - -**削減内容:** -``` -Branches: 6+ → 2 -TLS変数: 多数 → 1つ (mag) -Stack frame: Heavy → Minimal (inline候補) -Hot path cycles: ~20-30 → ~5-8 (60-70%削減) 🎯 -``` - -**タスクリスト:** - -- [ ] 3-1. `hak_tiny_alloc_v3()` 実装 - - [ ] Single-tier magazine hot path - - [ ] Branchless size-to-class - - [ ] Minimal stack frame - -- [ ] 3-2. `hak_tiny_alloc_slow_v3()` 簡素化 - - [ ] 2-tier fallback (magazine refill → superslab) - - [ ] Remove HotMag/TLS list/fast_pop complexity - - [ ] `__attribute__((cold, noinline))` - -- [ ] 3-3. Magazine refill最適化 - - [ ] `mag_refill_from_superslab()` 専用関数 - - [ ] Batch refill (32-64 items) - - [ ] Zero overhead on hit - -- [ ] 3-4. ベンチマーク測定 - - [ ] `./bench_random_mixed_hakmem 200000 400 1` - - [ ] 目標: **17.5-18.0 M ops/sec (+5-10%)** 🎯 - - [ ] vs mimalloc: 72-74% - ---- - -### Phase 4: 関数インライン化 (Phase 3成功後) - -**Phase 4: インライン化** -- `hak_tiny_alloc_v3` → `malloc` (force inline) -- `hak_tiny_free` → `free` (force inline) -- 目標: +2-3% (18.0-19.0 M ops/sec) - ---- - -## 🎯 マイルストーン - -| Phase | Target Throughput | Actual Result | vs mimalloc | Status | -|-------|-------------------|---------------|-------------|--------| -| Baseline | 16.24 M ops/sec | 16.24 M ops/sec | 67% | ✅ Baseline | -| ~~Phase 1 (単体)~~ | ~~18.6 M ops/sec~~ | ~~16.34 M ops/sec~~ | ~~67%~~ | ❌ **FAILED** (-0.73%) | -| Phase 2 (単体) | ~16.2 M ops/sec | 15.70 M ops/sec | 65% | ✅ **完了** (-3.3%, 予想通り) | -| **Phase 2+1 Combined** | ~~18.4-18.6 M ops/sec~~ | **16.53 M ops/sec** | **68%** | ✅ **完了** (+1.8%) | -| ~~Phase 3 (v3 alloc)~~ | ~~17.5-18.0 M ops/sec~~ | ~~16.57 M ops/sec~~ | ~~68%~~ | ❌ **FAILED** (+0.24%) | -| Phase 4 (インライン化) | 18.0-19.0 M ops/sec | - | 74-79% | ⏳ TODO | -| **Ultimate Goal** | **22-24 M ops/sec** | - | **90-100%** | 🌟 Long-term Target | - -**Phase 2+1の洞察 (2025-11-01 21:30):** -- ✅ Phase 2+1は**理論通り動作** (+1.8% 改善) -- ❌ 期待値(+12-13%)との乖離は**Mid MT mutexコスト見積もり誤り**が原因 -- 💡 シングルスレッド、空registryでは mutex は超軽量(~15 cycles) -- 🎯 **次の主戦場**: malloc/alloc側の27% overheadを攻める! - ---- - -## 📁 関連ドキュメント - -- ✅ **perf分析レポート**: `docs/PERF_ANALYSIS_TINY_MIXED.md` - - 詳細なボトルネック分析 - - perf annotate結果 - - 最適化ロードマップ - -- 📊 **ベンチマーク比較**: 他のAIちゃんのレビュー - - Mixed: HAKMEM 66.7% of mimalloc (weak) - - Mid MT: HAKMEM 129.4% of mimalloc (strong) - ---- - -## 💡 Next Action (今すぐやること) - -### 最初のステップ: free() Fast TLS Check実装 - -```bash -# 1. hakmem.c のfree()を開いて、Fast TLS checkを追加 -vim core/hakmem.c - -# 2. 変更箇所 -# free() の冒頭 (guard check後) に: -# - TLS Tiny heap範囲チェック -# - Hit時は直接 hak_tiny_free() -# - Miss時は既存フロー - -# 3. ビルド & ベンチマーク -make bench_random_mixed_hakmem -./bench_random_mixed_hakmem 200000 400 1 - -# 4. perf確認 -perf record -F 999 -g ./bench_random_mixed_hakmem 200000 400 1 -perf report --stdio -n --percent-limit 1 -``` - ---- - -## 🔥 モチベーション - -**目標**: Tiny Mixed Workloadで mimalloc に匹敵する性能 - -**現状**: -- Mid MT: **138% of mimalloc** ✅ (Already winning!) -- Tiny Mixed: **68% of mimalloc** ⚠️ (Need improvement!) - -**今日の目標**: -- Phase 1実装で **77% of mimalloc** (16.46 → 18.6 M ops/sec) - -**Let's optimize! 🚀** - ---- - -## 🚀 Phase 4: 構造的最適化戦略(2025-11-02策定) - -### 戦略概要 - -**目標**: mimalloc比90-100%到達(現状68% → 22-24 M ops/sec) - -**核心アプローチ**: -1. **3層への統合**(現在の6-7層を削減) -2. **TLS-BUMP即値化**(hot-class専用最適化) -3. **段階的実装**(即効 → 中期 → 長期) - -### Phase 4-A: 即効施策(今週実装) - -#### A-1: TLS-BUMP即値化 + hot-class強化 ⚡ -**期待効果**: +5-8%(16.53 → 17.5-18.0 M ops/sec) - -**実装内容**: -- `g_ultra_bump_shadow`有効化(現在無効) -- hot-class(16/32/64B)を即値化関数に書き換え -- 分岐ゼロ化(cmov版も検討) - -**変更ファイル**: -- `core/hakmem_tiny_hot_pop.inc.h` - 即値化版関数 -- `core/hakmem_tiny.c` - BUMP有効化 -- `core/hakmem_tiny_config.c` - デフォルト設定 - -**コード例**: -```c -// Before (現在) -void* head = g_fast_head[class_idx]; -if (head) { - g_fast_head[class_idx] = *(void**)head; - return head; -} - -// After (A-1) -uint8_t* p = g_tls_bcur[0]; -uint8_t* n = p + 16; // ← 即値! -if (likely(n <= g_tls_bend[0])) { - g_tls_bcur[0] = n; - return p; // ← 分岐1つ、TLS書き込み1回 -} -return tiny_bump_refill_cold(0); // noinline -``` - -#### A-2: 小マガジン容量最適化 📦 -**期待効果**: +3-5% - -**実装内容**: -- class 0-2(8/16/32B): 128エントリ固定 -- class 3+(64B+): 64エントリ(現状維持) -- L1ヒット率向上、ワーキングセット最適化 - -**変更ファイル**: -- `core/hakmem_tiny_magazine.h` - 容量定数 - -### Phase 4-B: 中期施策(1-2週間) - -#### B-1: 3層への統合リファクタリング 🏗️ -**期待効果**: +10-15%(累積+20-25%) - -**実装内容**: -```c -void* hak_tiny_alloc_v4(size_t size) { - int k = hak_tiny_size_to_class(size); - if (unlikely(k < 0)) return NULL; - - // Layer 1: TLS-BUMP (hot-class専用、即値化) - if (g_hot_alloc_fn[k]) { - void* p = g_hot_alloc_fn[k](); - if (likely(p)) return p; - } - - // Layer 2: 小マガジン128 - TinyTLSMag* mag = &g_tls_mags[k]; - if (likely(mag->top > 0)) { - return mag->items[--mag->top].ptr; - } - - // Layer 3: Slow (全部noinline/cold) - return hak_tiny_alloc_slow_v4(size, k); -} -``` - -**削減対象**: -- ❌ HAKMEM_TINY_BENCH_FASTPATH(SLL系) -- ❌ TinyHotMag複数層 -- ❌ wrapper context handling(slow pathへ) -- ❌ 過剰なTLS変数(bcur/bendのみ保持) - -#### B-2: ACE簡素化(4ノブ×4状態) -**期待効果**: p95安定化、ホットパス干渉除去 - -**実装内容**: -- ノブ4つ: BATCH, HOT_THRESHOLD, drain_mask, slab_lg -- 状態4つ: STEADY, BURST, REMOTE_HEAVY, MEM_TIGHT -- tick=150ms、観測は1/16Kサンプル -- ホットパス完全非干渉 - -### Phase 4-C: 長期施策(2-4週間) - -#### C-1: Mid/Large TL-Segment -**期待効果**: Mid/Large単スレで2×改善 - -**実装内容**: -- 4-16KBページ単位のTLバンプ -- ページ内free-list(連結生成最小化) -- ≥64KBは直map再利用キャッシュ(LRU 64本) - -#### C-2: per-core arena + SPSC remote queue -**期待効果**: MT競合削減、2-3×改善 - -**実装内容**: -- スレッド→core固定 -- cross-thread freeはSPSCリング -- allocのついでにdrain(バッチ128-256) -- レジストリcoreシャード化 - -### マイルストーン更新 - -| Phase | Target | Expected Result | vs mimalloc | Status | -|-------|--------|-----------------|-------------|---------| -| Baseline | 16.24 M | 16.24 M | 67% | ✅ Baseline | -| Phase 2+1 | 18.4-18.6 M | **16.53 M** | **68%** | ✅ 完了 (+1.8%) | -| ~~Phase 3~~ | ~~17.5-18.0 M~~ | ~~16.57 M~~ | ~~68%~~ | ❌ 失敗 (+0.24%) | -| **Phase 4-A** | **17.5-18.5 M** | - | **72-76%** | ⏳ **実装中** | -| Phase 4-B | 19.0-20.0 M | - | 78-83% | 📋 設計完了 | -| Phase 4-C | 22-24 M | - | 90-100% | 📐 構想中 | - -### 48時間ロードマップ - -**Day 1 (今日)**: -1. ✅ ChatGPT Pro相談完了 -2. ✅ ドキュメント更新 -3. 🔧 Phase 4-A1実装(TLS-BUMP即値化) -4. 🔧 ビルド & ベンチマーク - -**Day 2 (明日)**: -1. 📊 Phase 4-A1結果分析 -2. 🔧 Phase 4-A2実装(小マガジン128) -3. 📐 Phase 4-B詳細設計 -4. 🚀 Phase 4-B実装開始判断 - -### 設計原則 - -1. **2レジスタ経路死守**: `bcur/bend`だけでalloc完結 -2. **層は最小3段**: `TLS-BUMP → 小マガジン → Slow` -3. **ホット/コールド完全分離**: データもコードも64B境界分離 -4. **統計はサンプルのみ**: 1/16384、ホットパスに書き込みなし -5. **ヘッダ非更新**: slowで同期、allocはTLSのみ - -### 受け入れ基準 - -- Tiny-Hot 32/64/128B: **mimalloc ≥90-110%** -- Random Mixed: **mimalloc ≥90-105%**(p95安定) -- Mid/Large単: **≥80-100%**(段階的) -- Mid/Large MT: **×2改善** → 20-30%差まで短縮 -- RSS: **予算±10%内**、MEM_TIGHTで守る - ---- - ---- - -## 🚀 Phase 5: mimalloc 分析 & Direct Page Cache 実装 - -### Phase 5 Overview - -**mimalloc 分析完了 (2025-11-03):** -- 47% Gap の根本原因を特定 -- 3つの詳細レポート作成 -- Phase 3/4-A1 の教訓を確認: linked-list は最適 - -**Key Findings:** -1. **Direct Page Cache (O(1))** が最大のボトルネック: +15-20% -2. mimalloc も intrusive linked list を使用(Phase 3 の結論は正しい) -3. Gap はマイクロ最適化から来る(データ構造選択ではない) - ---- - -### Phase 5-A: Direct Page Cache 実装 - -**Goal:** サイズ→ページ lookup を O(log n) から O(1) に - -**Current (HAKMEM):** -```c -// Binary search through size classes - O(log n) -int class_idx = hak_tiny_size_to_class(size); // 3-5 comparisons -TinySlab* slab = g_tiny_pool.free_slabs[class_idx]; -``` - -**Target (mimalloc-style):** -```c -// Direct array index - O(1) -TinySlab* slab = g_tiny_pool.slabs_direct[size >> 3]; // 1 cycle! -``` - -**Implementation Plan:** - -**5-A-1. データ構造拡張** -```c -// core/hakmem_tiny.h -typedef struct { - TinySlab* free_slabs[TINY_NUM_CLASSES]; // Existing - TinySlab* full_slabs[TINY_NUM_CLASSES]; // Existing - TinySlab* slabs_direct[129]; // NEW: Direct cache (8-1024B) - // ... existing fields -} TinyPool; -``` - -**5-A-2. Direct cache の更新** -```c -// Slab allocation時に direct cache を populate -static TinySlab* allocate_new_slab(int class_idx) { - TinySlab* slab = /* ... existing allocation ... */; - - // NEW: Populate direct cache for this size class - size_t block_size = g_tiny_class_sizes[class_idx]; - for (size_t sz = block_size; sz < block_size + 8 && sz <= 1024; sz++) { - int idx = sz >> 3; // size / 8 - if (g_tiny_pool.slabs_direct[idx] == NULL) { - g_tiny_pool.slabs_direct[idx] = slab; - } - } - - return slab; -} - -// Slab exhaustion時に direct cache をクリア -static void move_to_full_list(int class_idx, TinySlab* slab) { - /* ... existing code ... */ - - // NEW: Clear direct cache entries pointing to this slab - for (int i = 0; i < 129; i++) { - if (g_tiny_pool.slabs_direct[i] == slab) { - g_tiny_pool.slabs_direct[i] = NULL; - } - } -} -``` - -**5-A-3. Hot path 書き換え** -```c -void* hak_tiny_alloc(size_t size) { - if (size > 1024 || size == 0) return NULL; - - // NEW: O(1) direct cache lookup - int idx = size >> 3; // size / 8 - TinySlab* slab = g_tiny_pool.slabs_direct[idx]; - - if (__builtin_expect(slab != NULL, 1)) { - // Fast path: direct cache hit - void* ptr = pop_from_slab(slab); - if (__builtin_expect(ptr != NULL, 1)) { - return ptr; // ← 5 cycles saved! - } - // Slab exhausted, clear cache and fallback - g_tiny_pool.slabs_direct[idx] = NULL; - } - - // Slow path: fallback to existing binary search - int class_idx = hak_tiny_size_to_class(size); - return hak_tiny_alloc_slow(class_idx); -} -``` - -**Expected Results:** -- **Cycle reduction:** 5 cycles per allocation (binary search elimination) -- **Throughput:** +15-20% (16.53 → 19.0-19.8 M ops/sec) -- **Memory overhead:** 1032 bytes (129 pointers) -- **Risk:** Low (fallback to existing path on miss) - -**Timeline:** 1-2 days - ---- - -### Phase 5-B: Dual Free Lists (Next) - -**Goal:** Local/Remote free list 分離で atomic ops 削減 - -**Expected Results:** +10-15% additional - ---- - -### Phase 5-C: Branch Hints + Flags (Next) - -**Goal:** Predictable branch + bit-packed flags - -**Expected Results:** +5-8% additional - ---- - -## 📊 Phase 5 Roadmap - -| Phase | Impact | Effort | Risk | Status | -|-------|--------|--------|------|--------| -| 5-A: Direct Cache | +15-20% | 1-2d | Low | 🔜 Next | -| 5-B: Dual Free Lists | +10-15% | 3-5d | Med | ⏳ Pending | -| 5-C: Branch Hints | +5-8% | 1-2d | Low | ⏳ Pending | -| **Total** | **+45%** | **1-2w** | **Low** | **16.53 → 24.0 M ops/sec** | - - ---- - -## 🚀 Phase 6: Learning-Based Tiny Allocator (2025-11-02~) - -### 戦略: "Simple Front + Smart Back" (Mid-Large HAKX の真似) - -**背景:** -- Phase 5-B 失敗: Magazine unification で -71% 💀 -- 包括的ベンチマークで根本原因特定: **Fast path が複雑すぎる** -- Mid-Large HAKX は +171% で成功 → 同じアプローチを Tiny に適用 - -### 目標 -- **Phase 1 (1週間)**: Ultra-Simple Fast Path → System の 70-80% (95-108 M ops/sec) -- **Phase 2 (1週間)**: 学習層追加 → System の 80-90% (108-122 M ops/sec) -- **Phase 3 (1週間)**: メモリ効率最適化 → System 同等速度 + メモリで勝つ 🏆 - -### 設計コンセプト - -#### Front: Ultra-Simple (System tcache 風) -```c -void* hak_tiny_alloc(size_t size) { - int cls = size_to_class_inline(size); - void** head = &g_tls_cache[cls]; - void* ptr = *head; - if (ptr) { - *head = *(void**)ptr; // 3-4 命令のみ! - return ptr; - } - return hak_tiny_alloc_slow_adaptive(size, cls); -} -``` - -#### Back: Smart (学習層) -- **Class Hotness Tracking**: どのサイズが hot/cold か学習 -- **動的キャッシュ容量調整**: Hot → 256 slots, Cold → 16 slots -- **Adaptive Refill Count**: Miss rate に応じて 16-128 blocks - -### Phase 1: Ultra-Simple Fast Path (進行中) - -**実装内容:** -1. `core/hakmem_tiny_simple.c` 新規作成 -2. TLS Free List ベースの fast path (3-4 命令) -3. SuperSlab からの refill (既存を流用) - -**ファイル:** -- `core/hakmem_tiny_simple.c` - シンプル版 Tiny allocator -- `core/hakmem_tiny_simple.h` - ヘッダ - -**ベンチマーク:** -- `bench_tiny_hot` で測定 -- 目標: System の 70-80% - -### 成功の鍵 - -1. **Fast path を System tcache と同等に** (3-4 命令) -2. **学習層で差別化** (動的容量調整) -3. **Mid-Large の成功パターンを適用** (+171% の実績) - -### 関連ドキュメント -- [`benchmarks/results/TINY_PERFORMANCE_ANALYSIS.md`](benchmarks/results/TINY_PERFORMANCE_ANALYSIS.md) - 根本原因分析 -- [`benchmarks/results/BENCHMARK_SUMMARY_2025_11_02.md`](benchmarks/results/BENCHMARK_SUMMARY_2025_11_02.md) - 包括的ベンチマーク結果 diff --git a/CURRENT_TASK_FULL.md b/CURRENT_TASK_FULL.md new file mode 100644 index 00000000..38c54d02 --- /dev/null +++ b/CURRENT_TASK_FULL.md @@ -0,0 +1,1855 @@ +# Current Task (2025-11-07) + +## 🔴 Phase 6-2.4: SuperSlab Guess Loop SEGV Fix (IN PROGRESS) + +### 問題 +`bench_random_mixed_hakmem` と `bench_mid_large_mt_hakmem` が即座に SEGV + +**再現:** +```bash +./bench_random_mixed_hakmem 50000 2048 1234567 # → Exit 139 (SEGV) +./bench_mid_large_mt_hakmem 2 10000 512 42 # → Exit 139 (SEGV) +``` + +### 根本原因(Ultrathink Task完全解析) + +**Location:** `core/box/hak_free_api.inc.h:92-95` (guess loop) + +**The Bug:** +```c +for (int lg=21; lg>=20; lg--) { + SuperSlab* guess=(SuperSlab*)((uintptr_t)ptr & ~mask); + if (guess && guess->magic==SUPERSLAB_MAGIC) { // ← SEGV + // guess が unmapped memory → dereference で SEGV + } +} +``` + +**Why SEGV:** +1. Registry lookup 失敗(alloc が SuperSlab 以外から) +2. Guess loop で 1MB/2MB align した `guess` を計算 +3. メモリマップ検証なし +4. `guess->magic` で unmapped memory dereference → **SEGV** + +**Why Benchmark Differences:** +- **Larson** (✅ works): All from SuperSlab → registry hit → guess loop スキップ +- **random_mixed** (❌ SEGV): Diverse sizes → non-SuperSlab allocs → guess loop → SEGV +- **mid_large_mt** (❌ SEGV): Large allocs → non-SuperSlab → guess loop → SEGV + +**Why LD_PRELOAD Works:** +- `hak_core_init.inc.h:119-121` で SuperSlab をデフォルト無効化 +- → SS-first path スキップ → guess loop 回避 → SEGV なし + +### 修正試行1: Guess Loop 削除 ❌ + +**Applied:** `core/box/hak_free_api.inc.h:92-95` 削除 + +**Result:** Still SEGV(別の箇所に問題あり) + +### 次のアクション +- [ ] SEGV の新しい場所を特定(gdb/ASan) +- [ ] Registry lookup が失敗する根本原因を調査 +- [ ] Complete report: `SEGV_ROOT_CAUSE_COMPLETE.md` + +### 🐛 新しい観測 (2025-11-07 19:10) + +- `HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0` で `bench_random_mixed_hakmem` を gdb 監視。class 7 の `TinySlabMeta` (`meta@0x7ffff6a00060`) にハードウェアウォッチを張ったところ、`sll_refill_batch_from_ss` 内で `meta->freelist` が `0x00000000000000e2` に化ける瞬間を捕捉。 +- 同じ SuperSlab 上の実ブロック (例: `0x7ffff6a3ec00`) の先頭ワードにウォッチを追加すると、`hak_tiny_free_superslab` → `trc_splice_to_sll` までは正しい next ポインタが書かれているが、その後ユーザープログラム (`bench_random_mixed.c` が `slot[idx][0] = ...` を書く地点) で 1byte 書き込みが入り、先頭ワードが `0xe2` に上書きされる。 +- つまり「まだアプリに貸し出しているブロック」が freelist に再露出しており、`sll_refill_batch_from_ss` が `*(void**)node` を読んだ瞬間に利用者データをポインタとして扱って SEGV になっている。 +- 該当 run では direct freelist push (`hak_tiny_free_superslab`) で `*(void**)ptr = prev` が実行されているログも取れているため、別経路(TLS spill / bg spill / remote drain)で stale head が復活している可能性が高い。 +- Fail-Fast instrumentation(`trc_pop_from_freelist()` に SuperSlab 範囲チェックを追加)を入れたところ、`[TRC_FAILFAST] stage=freelist_next cls=7 node=0x7eab7b20fc53 base=0x7eab7b200000 limit=0x7eab7b400000` で即座に abort。`meta->freelist` からポップしたノードの「次ポインタ」が SuperSlab 範囲外(= ユーザー書き換え)であることが確認できた。 +- `HAKMEM_TINY_REFILL_FAILFAST=2` で `tiny_failfast_log()` を `hak_tiny_free_superslab` / TLS spill / BG spill / remote drain の各箇所に挿入。ログを見ると **すべての問題ノードが `stage=free_local_box`(= 同一スレッド free)で登録** されており、`node=0x7e37d560fc53` のように **64B 非整列のアドレスが freelist に入っている** ことがわかった。 +- `HAKMEM_TINY_BG_SPILL=0` / `HAKMEM_TINY_TLS_LIST=0` / `HAKMEM_TINY_FAST_CAP=0` などの箱単位 A/B を実施しても Fail-Fast は継続。クラス 7 だけでなくクラス 6 でも `node=0x15` のように壊れた値が freelist に現れるため、原因は spill/remote ではなく「同一スレッド free に渡ってくる `ptr` 自体が壊れている(= ユーザが持っているポインタが既にズレている)」ラインが濃厚。 +- `tiny_free_local_box()` に SuperSlab/アライン検証を噛ませ、さらに `tiny_debug_track_alloc_ret()` でも配布直後のポインタを検証した結果、**割り当て段階で既に壊れたポインタを返している** ことが確定。 + - 例: `[TRC_FAILFAST_PTR] stage=alloc_ret_range cls=7 slab_idx=0 ptr=0x7ffff6a0fc00 reason=out_of_capacity base=0x7ffff6a00000 limit=0x7ffff6c00000 cap=63 used=63 offset=64512`(= capacity 個目のブロック)。 + - 例: `[TRC_FAILFAST_PTR] stage=alloc_ret_align cls=7 slab_idx=0 ptr=0x7ffff6a0f835 reason=misaligned ... cap=63 used=62 offset=63541`(1024B 未満の端数 709 を含む異常アドレス)。 + → `meta->used` と `meta->capacity` の境界処理、または `slab_idx==0` のヘッダ調整あたりで off-by-one / 加算漏れが起きており、存在しないブロックを線形 carve で組み立てている疑いが濃厚。 + +**次のアクション** +1. `sll_refill_batch_from_ss` に Fail-Fast を追加し、`meta->freelist` / `*(void**)node` が SuperSlab 範囲・アラインメント外だった場合に即座にログ&アボート(class, slab_idx, node, next, remote_heads も記録)。 +2. `hak_tiny_free_superslab` / `tls_list_spill_excess` / `bg_spill_drain_class` など `meta->freelist = node` を行う箇所で、`prev` が当該 SuperSlab 範囲かどうかをチェックするワンショットログを差し込み、どの経路で stale pointer が混入しているか切り分ける。 +3. 計測時に `HAKMEM_TINY_BG_SPILL=0`, `HAKMEM_TINY_TLS_LIST=0`, `HAKMEM_TINY_FAST_CAP` などを個別に OFF にして A/B。どの front/ spill 経路が二重登録を起こすかを特定してから修正を入れる。 + +--- + +## 📊 ベンチマーク行列サマリ (2025-11-07) + +実施: Larson + Suite(random_mixed / mid_large_mt / vm_mixed / tiny_hot)を system / mimalloc / HAKMEM で横並び計測し、CSV保存。 +- 保存先(Larson): `bench_results/larson/20251107_131427/results.csv` +- 保存先(Suite) : `bench_results/suite//results.csv` + +要点(この環境) +- Larson 4T: HAKMEM ≒ system ≒ mimalloc(≈3.35M ops/s)→ 上限到達(勝ち) +- Larson 1T: HAKMEM は差 ≈9–11%(3.03M vs 3.35M)→ 詰めれば勝ち筋 +- random_mixed(16–1024B): HAKMEM ≪ system/mimalloc(例: 5.9M vs 53–56M)→ 大差(要対策) +- mid_large_mt(8–32KiB, MT): HAKMEM ≪ system/mimalloc(1.05M vs 8.8–9.0M)→ 大差(要対策) +- vm_mixed(512KB–<2MB): HAKMEM ≪ system(~0.137M)→ 大差(要対策) +- tiny_hot(32B/64B): HAKMEM 80–85M vs system/mimalloc ~181–186M → 1/2水準(要対策) + +ログとスクリプト +- Larson 行列実行: `scripts/bench_larson_matrix.sh`(CSV + raw 保存) +- Suite 一括実行: `scripts/bench_suite_matrix.sh`(CSV + raw 保存) + +--- + +## 🏁「全部勝つ」プラン(優先度順の打ち手) + +Phase A(即効A/B、1日) +- Larson 1T(9–11%差の解消) + - 特化分岐 ON: `HAKMEM_TINY_SPECIALIZE_MASK=0x0F`(8/16/32/64B)で branch 減(Box 5) + - adopt=OFF(1T)、FAST_CAP=16/32 A/B、PGO(tiny_hot/larson)で最短パス強化 + - 期待: 3.03M → 3.10–3.18M(system 3.35M に接近) + +- tiny_hot(80M → 120M+ を目標) + - Strict Front/branchless pop の微最適化(Box 5 内だけ、境界不変) + - SLL cap/REFILL のホット帯 A/B(`REFILL_COUNT_HOT=48/64`, `FAST_CAP=16/32`) + - 期待: +30–40%(単体ベンチでの指標) + +Phase B(中規模、2–3日) +- random_mixed(5.9M → 30–40M を目標) + - TLS/SLL ヒット率向上(Front‑Gate Box の早期 return、MAG→SLL 経路の分岐簡素化) + - free 経路の境界コスト削減(Box 2/3 内で副作用封じ、Box 4 で一括処理) + - 特化 + PGO の組み合わせを sweep(スクリプト化) + +- mid_large_mt(1.05M → 6–8M を目標) + - L2.5/Large のバッチ/Flush/Harvest のチューニング(箱内のみ、境界不変) + - Back‑pressure(bg remote / flush 閾値)を MT に合わせて最適化 + - 大サイズ再利用の BigCache/L25 ゲートを A/B(`HAKMEM_BIGCACHE_*`) + +- vm_mixed(~0.137M → 1–2M を目標) + - 512KB–<2MB 帯の再利用強化(BigCache‑L25 方向) + - mmap/munmap 頻度低減のためのバッチ化・しきい値調整(箱内) + +Phase C(検証と固定化) +- 各ベンチで 5–10 回の連続実行 → 中央値を CSV 追記、グラフ化 +- 勝ち構成を `bench_results_archive/` に保存、ENV プリセット化(profiles/*.env) + +--- + +## TODO(実行リスト:当面のアクション) +- [ ] Larson 1T: SPECIALIZE_MASK/FAST_CAP/PGO の A/B を実施し CSV 追記 +- [ ] tiny_hot: Strict Front + REFILL/HOT の sweep(32/64B) +- [ ] random_mixed: Front‑Gate Box の早期 return A/B、free 境界軽量化 A/B +- [ ] mid_large_mt: L25/BigCache の閾値・バッチ・bg_remote A/B +- [ ] vm_mixed: L2.5 帯の再利用ゲート/バッチ化 A/B +- [ ] スイート行列(scripts/bench_suite_matrix.sh)の繰返し回数を増やし中央値取得 + +## ✅ Phase 6-2.4: SuperSlab SEGV fix(2025-11-07) + +現象(修正前) +- Tiny alloc は成功するが、free 時に SuperSlab を見つけられず `magic=0` → invalid pointer → SEGV。 +- Direct-link と LD_PRELOAD で挙動が異なり、前者は `g_invalid_free_mode=1` により skip→リーク→崩壊、後者は libc へフォールバックで一見動作(実際はリーク)。 + +修正内容 +- core/box/hak_free_api.inc.h + - Guess ループ(未マップ領域への生読み取り)を削除。 + - Header 参照前に `hak_is_memory_readable()`(mincore ベース、fallback のみで使用)で可読性を確認。 +- core/hakmem_internal.h + - `hak_is_memory_readable(void*)` を追加(mincore 1byte で可読チェック)。 +- レジストリの同期も正規化済(Phase 6-2.3 補強):`SuperRegEntry.base` を atomic 化、acq/rel 統一。 + +検証(直近実測) +- random_mixed(cycles=200k, ws=4096): 2.84M ops/s(修正前: SEGV) +- random_mixed(cycles=400k, ws=8192): 2.92M ops/s(修正前: SEGV) +- mid_large_mt(threads=4, cycles=40k, ws=2048): 2.00M ops/s(修正前: SEGV) +- Larson 4T: 0.838M ops/s → 0.838M ops/s(変化なし、安定) + +備考 +- mincore は fallback 経路のみで使用され、ホットパスに入らないため性能影響は無視できる水準。 +- 追加のデバッグノブ:`HAKMEM_SUPER_REG_DEBUG=1`(register/unregister 一発)/ `HAKMEM_SUPER_REG_REQTRACE=1`(invalid-magic 時に 1MB/2MB base+magic を一発) + + +## 🔍 SuperSlab registry デバッグ進捗 (2025-11-07) +- ✅ `SuperRegEntry.base` を `_Atomic uintptr_t` 化し、登録/解除/lookup で acquire/release を正規化。 +- ✅ 追加ノブ: + - `HAKMEM_SUPER_REG_DEBUG=1` → register/unregister を1行ログ出力(例: `[SUPER_REG] register base=...`)。 + - `HAKMEM_SUPER_REG_REQTRACE=1` → invalid-magic 時に 1MB/2MB の base/magic を一発表示。 + +現状観測 +- `bench_random_mixed_hakmem` / `bench_mid_large_mt_hakmem` は短ランでもセグフォ再現。stderr 冒頭は初期化ログと大量の `[SUPER_REG] register ...` のみで、unregister は未視認。 +- `HAKMEM_SUPER_REG_REQTRACE=1` を ON にした直リンク短ランでは、現段階で `[SUPER_REG_REQTRACE] ...` 行は出ず(= header 経由の invalid-magic 発火前に崩れている)。 +- Asan PRELOAD (`LD_PRELOAD=libasan.so:libhakmem_asan.so`) で system 版を実行し stack/ログを `/tmp/asan_{rand,midmt,vm}_sys.*` に保存済み。次は stack 抽出と CURRENT_TASK への貼付を予定。 + +次ステップ +1. 直リンク短ラン + `HAKMEM_SUPER_REG_REQTRACE=1` + `SIGUSR2`(Tiny Debug Ring)の組合せで、`hak_super_lookup` 前後の順序を突き止める。 +2. `/tmp/asan_*` ログから `[SUPER_REG] register` の時系列と Asan stack を抽出し、free→lookup→unregister の競合がないか記録。 +3. 必要に応じて `hak_tiny_free` の入口に Fail-Fast(SLL上限/SS範囲アサート)を追加し、異常を早期に顕在化させる。 + +## 🚨 SuperSlab ON での直リンク random_mixed 再現 (2025-11-07) + +再現手順(直リンク・短ラン) +``` +env HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0 \ + ./bench_random_mixed_hakmem 200000 4096 1234567 +``` +結果: SEGV(EXIT 139)。stderr 先頭には [ELO]/[Batch]/[ACE] と大量の `[SUPER_REG] register ...` が出力されるが、`unregister` は未視認。 + +Asan PRELOAD での stack 採取(system 版) +``` +make -j asan-shared-alloc +env HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0 \ + LD_PRELOAD="$(gcc -print-file-name=libasan.so):$PWD/libhakmem_asan.so" \ + ASAN_OPTIONS="halt_on_error=1:abort_on_error=1:alloc_dealloc_mismatch=1:detect_leaks=0:fast_unwind_on_malloc=0" \ + ./bench_random_mixed_system 200000 4096 1234567 2> /tmp/asan_rand_ss.err +``` +期待: invalid free/lookup 近傍のバックトレース + `[SUPER_REG]` の登録ログを取得。 + +reqtrace + リング強制(SEGV前の痕跡) +``` +env HAKMEM_TINY_USE_SUPERSLAB=1 HAKMEM_TINY_MEM_DIET=0 \ + HAKMEM_SUPER_REG_REQTRACE=1 HAKMEM_SUPER_REG_DEBUG=1 \ + ./bench_random_mixed_hakmem 50000 2048 1234567 2> /tmp/rand_reqtrace_ss.err & pid=$!; \ + sleep 1; kill -USR2 $pid; wait $pid +``` + +メモ: 上記のログ抽出・stack 解析後、必要に応じて free 入口の Fail‑Fast 追加や、lookup の lg/align 判定の一時バイアスで切り分け予定。 + +## ✅ Phase 6-2.3: Active Counter Bug Fix (2025-11-07) + +### 問題発見 +HAKMEM 直リンク 4T で `free(): invalid pointer` クラッシュを発見: + +**再現手順:** +```bash +./larson_hakmem 10 8 128 1024 1 12345 4 +# → Exit 134: free(): invalid pointer +# → [DEBUG] superslab_refill returned NULL (OOM) +``` + +**症状:** +- System malloc/mimalloc: 3.3M ops/s ✅ +- HAKMEM 1T: 838K ops/s (-75%) ⚠️ +- HAKMEM 4T: 起動直後にクラッシュ ❌ + +### 根本原因(Ultrathink Task Agent 調査) +**Active Counter Double-Decrement in P0 Batch Refill** + +`core/hakmem_tiny_refill_p0.inc.h:103` で freelist から TLS cache にブロックを移動する際、active counter をインクリメントし忘れ: + +``` +1. Free → カウンタ減算 ✅ +2. Remote drain → freelist に追加(カウンタ変更なし) ✅ +3. P0 batch refill → TLS に移動(カウンタ増加忘れ)❌ ← バグ! +4. 次の Free → カウンタ減算 ❌ ← ダブルデクリメント! +``` + +結果:カウンタアンダーフロー → SuperSlab が「満杯」 → OOM → クラッシュ + +### 修正内容 +**File:** `core/hakmem_tiny_refill_p0.inc.h:103` + +```diff + trc_splice_to_sll(class_idx, &chain, &g_tls_sll_head[class_idx], &g_tls_sll_count[class_idx]); +-// NOTE: from_freelist は既に used/active 計上済みのブロックの再循環。 ++// FIX: Blocks from freelist were decremented when freed, must increment when allocated ++ss_active_add(tls->ss, from_freelist); +``` + +### 検証結果 + +| 設定 | 修正前 | 修正後 | +|------|--------|--------| +| デフォルト 4T | ❌ クラッシュ | ✅ 838K ops/s | +| 安定性(2回実行) | - | ✅ 同じスコア | + +### 残課題 +❌ **`HAKMEM_TINY_REFILL_COUNT_HOT=64` でクラッシュ再発** + +```bash +HAKMEM_TINY_REFILL_COUNT_HOT=64 ./larson_hakmem 10 8 128 1024 1 12345 4 +# → Exit 134: free(): invalid pointer (class=4 で OOM) +``` + +**暫定診断:** +- Class 0-3 が `want=64` で大量 refill → メモリ過剰消費 +- Class 4 がメモリ不足で OOM +- 原因: TLS cache 過剰蓄積またはメモリリーク? + +**次のアクション候補:** +1. TLS cache サイズ制限確認(`HAKMEM_TINY_FAST_CAP`) +2. メモリリーク詳細調査(valgrind) +3. デフォルト refill count と Class 0-3 vs 4 の比較 + +--- + +## 🎉 デバッグ完了 (2025-11-07 - Sanitizer) + +結論 +- HAKMEM allocator は ASan / UBSan で健全性を確認済み。メモリ破壊や未定義動作は検出されず、現状の箱境界は安全に動作。 + +検証ルート(便利ターゲット) +- ✅ `make asan-preload-run THREADS=4` — メモリ破壊チェック(安定; 非ASan本体+LD_PRELOADでASan先頭) +- ✅ `make ubsan-mailbox-run THREADS=4` — Mailbox/Remote 健全性チェック(安定) +- ✅ `make asan-preload-mailbox-lite THREADS=4` — 短時間の境界チェック(安定, 5s/CHPT=256, RemoteGuard+Ring) + +補足(運用ガイド) +- ASan直リンクの4Tは環境依存で Shadow 予約に失敗するため、当面は PRELOAD 方式を既定とし、Mailbox 有効系は UBSan を用いる。 +- CI/スクリプトは上記ターゲットを用いることで再起動ループの回避と安定検証が可能。 + +## 🆘 最優先: 強制終了/再起動ループの緊急対応(sanitizer/環境周り) + +現象 +- ベンチ実行や補助プロセス実行中に強制終了(SIGKILL/abort)し、環境全体が再起動ループに入ることがある。 +- ASan直リンク(`larson_hakmem_asan{,_alloc}`)の4T実行で高頻度に `ReserveShadowMemoryRange failed (ENOMEM)` が発生。 +- Ready/Mailbox ON + ASan ではプロセスが沈黙/強制終了するケースあり(stderr/outとも0バイト)。 + +暫定診断(根本原因候補) +- AddressSanitizer の Shadow メモリ確保が他領域と衝突し ENOMEM → ランタイムが abort。 +- LD_PRELOAD順序/ASLR/マップ数制限(`vm.max_map_count`)/LTO+最適化との相性で、MT下の Shadow 予約が不安定化。 +- Ready/Mailbox ON でメモリマップ挙動が変化し、ASan の reserve 失敗トリガを踏みやすい。 + +安全な回避経路(検証済み) +- ASan: 本体は非ASan(`larson_system`)、`LD_PRELOAD=$(gcc -print-file-name=libasan.so):./libhakmem_asan.so` でランタイム先頭ロード。 + - 4T 安定完走(Ready/Mailbox OFF): ~3.25M ops/s を確認。 +- UBSan: `make ubsan-larson-alloc` + Ready/Mailbox ON で 4T 完走(~3.35M ops/s)。 + +緊急対応の優先順位(Fail‑Safe) +1) 既定の「計測/検証」ルートを sanitizer-safe に切替(当面の安定化) + - ASanが必要: `scripts/run_larson_asan_preload.sh 4`(Ready/Mailbox OFF) + - Ready/Mailbox ON検証: UBSanに切替 `HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 ./larson_hakmem_ubsan_alloc …` +2) 再起動ループ回避の運用ガード + - 長時間ASan 4T禁止、まず短時間/小負荷(例: ch/thread 256, sleep 5)で段階実行。 + - CI/スクリプトで ASan 直リンク4Tをスキップし、LD_PRELOAD 方式へ統一。 +3) 根治(後追い・環境依存) + - オプション: `ASAN_OPTIONS=quarantine_size_mb=8:malloc_context_size=5` 等でメモリ圧縮(効果限定的)。 + - `vm.max_map_count` 引き上げ、ASLR制御、最適化/LTO無効化(要環境合意)。 + +当面の実行手順(コピペ可) +``` +# ASan(Ready/Mailbox OFF, 4T安定) +./scripts/run_larson_asan_preload.sh 4 + +# UBSan(Ready/Mailbox ON, 4T安定) +make -j ubsan-larson-alloc >/dev/null +HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 ./larson_hakmem_ubsan_alloc 10 8 128 1024 1 12345 4 +``` + +チェックリスト(PR/レビュー時) +- [ ] ASan直リンク4Tを実行していない(PRELOAD方式へ誘導)。 +- [ ] Ready/Mailbox ON の4Tは UBSan で計測している。 +- [ ] ベンチスクリプトで異常終了時に即時中断しループしない。 +- [ ] 影響範囲のログ(ワンショット/リング)を残すが常時多出力は避ける。 + +## 🔔 最新アップデート (2025-11-05 16:30) 🔥🔥🔥 + +### ✅ Registry 線形スキャン ボトルネック特定! + +**Perf 分析完了 → Root Cause 発見!** + +**測定結果 (Larson, threads=4):** +``` +HAKMEM: 3.62M ops/s (21.6% of system) +System: 16.76M ops/s (100%) +差: -78.4% 💀 +``` + +**Perf プロファイリング結果:** +``` +28.51% superslab_refill 💀💀💀 圧倒的ボトルネック + 2.58% exercise_heap (ベンチマーク本体) +``` + +**問題:** アロケータ (superslab_refill) がベンチマーク本体 (exercise_heap) より CPU time を消費! + +**Hot Instructions 分析 (perf annotate):** +``` +32.36% cmp $0x3ffff,%r11d ← 262,143 回ループ! +16.78% inc %r13d ← カウンタ++ +16.29% add $0x18,%rbx ← ポインタ進める +合計 97.65% の CPU time がループに集中 +``` + +**Root Cause (core/hakmem_tiny_free.inc:917):** +```c +for (int i = 0; i < SUPER_REG_SIZE && scanned < scan_max; i++) { + // ^^^^^^^^^^^ 262,144 エントリを線形スキャン! + SuperRegEntry* e = &g_super_reg[i]; + // ... class_idx 不一致でも全エントリをイテレート +} +``` + +**解決策: Per-class Registry** +```c +// Before: 全 class 混在 +SuperRegEntry g_super_reg[262144]; + +// After: class ごとに分離 +SuperRegEntry g_super_reg_by_class[TINY_NUM_CLASSES][4096]; +// 8 classes × 4096 entries = 32K total +``` + +**期待効果:** +- スキャン対象: 262,144 → 4,096 エントリ (-98.4%) +- **期待改善: +200-300%** (2.59M → 7.8-10.4M ops/s) +- **System malloc の 54-73% まで到達可能!** 🎯 + +**詳細レポート:** +- `PERF_ANALYSIS_2025_11_05.md` - 完全分析 + 実装プラン +- Commit: `859027e` "Perf Analysis: Registry 線形スキャンがボトルネック" +- Branch: `perf-analysis-2025-11-05` (pushed to private repo) + +**次のアクション:** +1. **Phase 1 実装** (1-2日): per-class registry に変更 + - `core/hakmem_super_registry.{h,c}` 構造変更 + - `core/hakmem_tiny_free.inc:917` スキャン簡素化 + - 目標: 2.59M → 7.8M ops/s (+3倍!) + +2. **Phase 2 実装** (追加 1日): 早期終了 + First-fit + - 最初の freelist 発見で即 return + - 目標: 7.8M → 11.7M ops/s (system の 82%!) + +**重要:** Box Refactor は既に動いている! +- Single-thread: 0.46M → 2.59M ops/s (+463%!) ✅ +- Multi-thread: 1.81M → 4.19M ops/s (+131%!) ✅ +- 外部AIの最適化も効いている (Option A: Inline TLS cache access) +- **ただし Registry スキャンがボトルネックで system には届かず** + +--- + +## 🔔 最新アップデート (2025-11-06) + +- Build 既定を Box Refactor(Phase 6-1.7)に切替済み。 + - Makefile に `-DHAKMEM_TINY_PHASE6_BOX_REFACTOR=1` を既定付与。 + - 旧系へ切替: `make BOX_REFACTOR_DEFAULT=0 larson_hakmem`。 +- Larson 2s/4T・5s/4T でセグフォ再発なしを確認(SLL-only, FAST_CAP=16, SS_ADOPT=1)。 +- 次フェーズ: mimalloc 対決(Larson)へ移行。Hot Tiny クラス向けの refill/binding 帯域最適化に集中。 + +推奨計測プロファイル(現時点) +- SLL-only Fast: `HAKMEM_TINY_TLS_SLL=1 HAKMEM_TINY_TLS_LIST=0 HAKMEM_TINY_HOTMAG=0` +- Fast tier: `HAKMEM_TINY_FAST_CAP=16` +- Refill: `HAKMEM_TINY_REFILL_COUNT_HOT=64`(A/B: 48/64) +- Pipeline: `HAKMEM_TINY_SS_ADOPT=1`(publish→mail→adopt 通電) + +再現コマンド(2s/4T, 5s/4T) +``` +HAKMEM_TINY_REFILL_COUNT_HOT=64 \ +HAKMEM_TINY_FAST_CAP=16 \ +HAKMEM_TINY_TRACE_RING=0 HAKMEM_SAFE_FREE=0 \ +HAKMEM_TINY_TLS_SLL=1 HAKMEM_TINY_TLS_LIST=0 HAKMEM_TINY_HOTMAG=0 \ +HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 \ +./larson_hakmem 2 8 128 1024 1 12345 4 + +./larson_hakmem 5 8 128 1024 1 12345 4 +``` + +デバッグ一発ログ(導通確認) +``` +HAKMEM_TINY_REFILL_OPT_DEBUG=1 \ +HAKMEM_TINY_TRACE_RING=0 HAKMEM_SAFE_FREE=0 \ +HAKMEM_TINY_TLS_SLL=1 HAKMEM_TINY_TLS_LIST=0 HAKMEM_TINY_HOTMAG=0 \ +HAKMEM_WRAP_TINY=1 HAKMEM_TINY_SS_ADOPT=1 \ +./larson_hakmem 2 8 128 1024 1 12345 4 +``` + +--- + +## 🔧 箱化フェーズ(進行中) + +現状(2025-11-06 21:xx) + +- Phase 1 完了(安全・即効の分離) + - 退出ダンプ([EXIT DEBUG]/SS会計)を箱化 + - `core/box/hak_exit_debug.inc.h` を導入し、`hakmem.c` から関数本体を除去 + - KPIユーティリティ(/proc, RSS など)を箱化 + - `core/box/hak_kpi_util.inc.h` を導入し、`hak_get_kpi()` を移動 + - ビルド・2s/4T スモーク OK(Throughput ≈ 4.19M ops/s, 回帰なし) + +- Free/Tiny/SS 導通の暫定診断(短ランでOOMを避けつつ) + - 追加カウンタ: `g_free_wrapper_calls`, `g_hak_tiny_free_calls`, `g_free_ss_enter`, `g_free_local_box_calls`, `g_free_remote_box_calls`, `g_ss_active_dec_calls` + - 短ラン結果: free() は大量に呼ばれているが、Tiny/SS側の free 経路には一度も到達していない(全て 0) + - OOM傾向はこの導通不全が主因。長時間ランは回避し、短ランで追跡継続。 + +次にやること(Phase 2: 中核APIの箱化 + 診断の最小追加) + +1) hak_alloc_at / hak_free_at の箱化(見通し改善・安全に戻せる設計) + - 新規: `core/box/hak_alloc_api.inc.h`(alloc本体) + - 新規: `core/box/hak_free_api.inc.h`(free本体) + - `hakmem.c` から本体を除去し、1 行 include へ差し替え + - 目的: `hakmem.c` を 500行級へ圧縮し、中央ハブとしての見通しを確保 + +2) Free 導通の最小スケスケ(短ラン限定・ワンショット) + - ENV: `HAKMEM_FREE_ROUTE_TRACE=1` で最初の N 件だけ分類ログ + - `super(registry ok/miss)` / `mid(hit/miss)` / `l25(hit/miss)` / `unknown` + - OOMを避けるため 2s/4T のみで実行。Tiny/SSへ届かない原因を最短で特定。 + +3) Phase 3 の見積もり(必要時) + - init/shutdown の箱化(`core/box/hak_core_init.inc.h`) + - 最終目標: `hakmem.c` を中央 include ハブ(~400–600行)に固定 + +A/B・戻せる設計 + +- すべて `.inc.h` の差し替え(1行)で段階導入。問題が出たら即時リバート可。 + +タイムライン(目安) + +- Phase 2: 1–2時間(箱化) + 1時間(2s/4T 短ラン×数回) +- Phase 3: 1–2時間(箱化) + 30分(スモーク) + + +## 🧱 本日の着手(L2.5/L2 キャッシュ → ベンチ) + +- 目的: vm/mixed での大サイズ(≥512KB)の再利用性を引き上げ、mimalloc/system に肉薄/逆転。 +- 仕様(箱理論): + - BigCache-L25 ゲート(A/B) + - env `HAKMEM_BIGCACHE_L25=1` で、512KB–<2MB のサイズも per-site BigCache を利用。 + - 境界1箇所:alloc/free のみ(他経路には侵食しない)。 + - Fail-Fast と戻せる設計:env で即時OFF可。 +- 実装: + - `core/hakmem.c` に BigCache-L25 ヒット/プット分岐を追加(A/Bフラグで制御)。 + - 既存 BigCache(≥2MB)は維持しつつ、L2.5 も同一箱を使って簡素化。 +- ベンチ: + - ハーネス復元(`bench_allocators_{hakmem,system}`)。 + - 4シナリオ × 3アロケータ(system/mimalloc/HAKMEM)× 5回の自動化を強化し、CSV保存。 + - シナリオ案: random_mixed(16–1024B, 1T), mid_large_mt(8–32KiB, 4T), larson(8–128B, 4T), redis-like(16–1024B, 1T, LD_PRELOAD) + - 出力: `bench_results/auto//*.csv`(ops/s, 備考列にENV) + - クイックCSV(5×4シナリオ×3アロケータ)を `bench_results_archive/` に保存。 + +次アクション: +1. `HAKMEM_BIGCACHE_L25=1` で quick_full_benchmark を再実行し、vm/mixed の改善を確認。 +2. 改善が見られれば、THPゲートとdecommitバッチのA/Bを追加実装(箱と境界は現行踏襲)。 + +## 🎯 次の主目標(mimalloc 対決: Larson/TinyHot) + +1) Hot Tiny リフィル最適化(Box 4 境界の探索コスト縮減) +- [ ] `HAKMEM_TINY_REFILL_COUNT_HOT={48,64}` を A/B。L1d miss と IPC を `perf stat` で取得。 +- [ ] `tiny_refill_try_fast()` の class<=3 優先化(MailBox→Sticky/Hot の順)と早期 return の徹底。 +- [ ] Drain のチェイン splice(済)を維持しつつ、不要ドレインの抑制(remote_pending のみ)。 + +2) ベンチ・スナップショット +- [ ] `scripts/bench_capture_now.sh` で現構成を保存(日付入り)。 +- [ ] `scripts/profiles/tinyhot_best.env` に `REFILL_COUNT_HOT`/`FAST_CAP` を追記しベストを固定。 + +3) 比較と回帰防止 +- [ ] Larson 2s/4s × threads=1/4/8 で hakmem vs mimalloc を測定・記録。 +- [ ] Guard/ASan を OFF のまま長時間(>30s)を1回通して安定性を確認。 + +4) 伸びなければ(次の一手) +- [ ] class<=3 の探索順再編(Sticky/Hot/Bench の順入替)を小さく A/B。 +- [ ] それでも平坦なら、Front(最前段)の前倒しポップ(SLL 事前 pop)を A/B(Box 5 内だけで完結)。 + + +## 🔔 最新アップデート (2025-11-06 19:40) + +- RemoteTrack Box(debug-only)を導入し、ノードの状態遷移(alloc→remote→drain→freelist)を追跡。矛盾時は `REMOTE_TRACK_MISMATCH`+BT を出力。 +- SlabHandle を全採用経路に統一:所有権取得→`slab_drain_remote_full()`→ゼロ確認→採用失敗時は release。Box2/Box3 の境界を1箇所へ集約。 +- TLS alloc 前に remote queue を opportunistic drain(`superslab_alloc_from_slab`)し、side-table 上に残るノードは guard モードで Fail-Fast。 +- guard 走行では `REMOTE_TRACK_MISMATCH stage=alloc_ret` がまだ発生。原因は「remote queue が drain されず、採用境界をすり抜けて TLS 返却されたノードが存在」。 +- 次手:採用境界に“remote 残があれば採用しない”ゲートを追加し、配布直前に side-table を再確認(guard 時のみ)して強制停止する。 + +## 🔔 最新アップデート (2025-11-04 12:20) + +直近の構造改善(箱の積み直し v2) + +1) SlabHandle Box(core/slab_handle.h, ~100行) +- 所有権取得・リモートキュードレイン・メタデータアクセスをカプセル化 +- 型安全なハンドル(valid==1 のときのみ drain/modify が可能) +- Invariant: drain/push/pop は「所有権取得後のみ」実行可(境界1箇所)。 + +2) 6 箇所のリファクタリング(SlabHandle 適用) +- tiny_refill.h: Sticky / Hot / Bench / Mailbox 採用箇所(4箇所)で、候補決定→SlabHandle 取得→remote_drain→bind の順へ整理 +- tiny_mmap_gate.h: Registry scan の採用箇所(1箇所)を SlabHandle 化 +- hakmem_tiny_free.inc: SuperSlab adopt path(1箇所)を SlabHandle 化 + +3) 所有権なし drain のバグ修正 +- hakmem_tiny_superslab.h:376 の `ss_remote_drain_light()` が ownership 無しで drain していた点を是正 +- 修正: `ss_owner_try_acquire()` で取得に成功した場合のみ `ss_remote_drain_to_freelist()` を実行 + +現状の問題(継続中) +- 依然 crash(fault_addr=0x6261)。Tiny Debug Ring にて以下を観測: + - [N] event=free_enter class=0 ptr=0x...28c0 + - [N+1] event=free_enter class=3 ptr=0x...28c0 ← 同一ポインタを異なる class で 2 回 free +- 結論: freelist 破損ではなく、クラス判定ミス or 二重 free(UAF)の可能性が高い。 +- Hypothesis: + - hak_super_lookup() の再マップ(旧 SS → 新 SS 同一アドレスで別 class)が、二重 free を“別 class”に見せている。 + - 実態は上位レイヤ(呼び出し側)の二重 free である可能性が高い。 +- 対応(デバッグ方針): + - `HAKMEM_SAFE_FREE=1` を既定ON推奨(デバッグ期間)。 + - SS free 境界でブロック整合チェック(slab_base/offset/size/容量)と freelist 簡易スキャン(<=64)で二重 free を検出。 + - SS/Tiny の二重ルックアップ比較(両者が同時に見つかり class が不一致ならリングに記録)。 + - `HAKMEM_SAFE_FREE_STRICT=1` なら Fail‑Fast(SIGUSR2)で即座に停止。 + +最優先課題は「FAST_CAP=0(fast-tier OFF)時に 4T Larson で再現する SuperSlab remote free の SEGV」を潰すことです。publish→mail→adopt は通電が確認できており、先に Box 2/3(Remote/Ownership)を箱単位で健全化します。その後、L2.5/L2 BigCache のA/Bを本番ハーネスで収集(CSV)します。 + +--- + +## 🚀 後段つよつよ大作戦(mimalloc 撃破作戦) + +目標(Objective) +- 「Larson(8–128B)」と「mid/mixed」代表ワークロードで mimalloc を撃破 or 同等に接近する。 + - 直近ターゲット(10秒計測) + - Larson 4T: ≥ 12–14M ops/s(段階目標)、最終:mimalloc≒16.7M ops/s に接近 + - Mid/Large MT 4T: systemの80%→100% 到達 + - Random Mixed 1T: 2–3x 改善(PF/sys 抑制で底上げ) + +作戦(Box Theory に基づく後段強化) +- Adopt/Ready 優先箱(取り出しO(1)) + - Ready List(per-class slab hint)を最前段で採用。publish/remote/first-free で push、refill で pop→bind。 + - A/B: `HAKMEM_TINY_READY=1`、cooldown=0、scan窓縮小(REG_SCAN_MAX=64/32/16)。 +- Registry/探索の削減箱 + - per-class registry の窓幅をさらにチューニング(64→32→16)。first‑fit で即帰還。 + - `HAKMEM_TINY_REG_SCAN_MAX` をマトリクスで最適点探索。 +- Superslab/Mmap Gate(must‑adopt‑before‑mmap 強化) + - adopt×2(yield前後)+ Ready→Mailbox→Registry の順固定。mmap は最終手段。 + - Gate内で sticky を先行、必要に応じて small ドレイン(所有権必須)。 +- L2.5/L2 BigCache(VM寄り) + - L2.5(512KB–<2MB)も per‑site BigCache A/B(`HAKMEM_BIGCACHE_L25=1`)。 + - 狭帯域(512KB–1MB)シナリオでヒット率を上げ、PF/sys を可視に低減。 + +可視化/計測(スクリプト整備済み) +- CSV出力マトリクス(reps=5/10, 10秒ラン) + - Larson triad: `benchmarks/scripts/run_larson_matrix.sh 2,10 1,4 REPS` + - Mid/Large MT: `benchmarks/scripts/run_mid_large_mt_matrix.sh 1,4 CYCLES WS REPS` + - Random Mixed: `benchmarks/scripts/run_random_mixed_matrix.sh CYCLES WS REPS` + - VM Mixed(L2.5 A/B): `benchmarks/scripts/run_vm_mixed_matrix.sh CYCLES WS REPS` + - Redis-like(LD_PRELOAD): `benchmarks/scripts/run_redis_matrix.sh THREADS CYCLES OPS REPS` +- perf stat(PF/dTLB/IPC/branches)を10秒ランに併記(Larson/Mid中心)。 + +TODO(短期ロードマップ) +1) Larson 2s→10s(1T/4T)で REG_SCAN_MAX × READY × ADOPT のA/B(CSV+perf) +2) Mid/Large MT 10s(1T/4T)で採用窓とGate強化の最適点探索(CSV+perf) +3) VM Mixed 狭帯域(512KB–1MB)で L25=ON/OFF の差を定量(CSV) +4) Redis LD_PRELOAD 安定化(Tiny安全モード→段階的拡張) +5) ベスト設定を `benchmarks/RESULTS_SNAPSHOT.md` に反映、`benchmarks/README.md` に推奨ENV追記 + +リスク/フォールバック +- READY/ADOPT はA/Bガード付き(env)で即時切替可。Gate強化も1箇所の境界内で適用/解除する。 +- L2.5 BigCache は狭帯域で先行検証(広帯域ではオーバーヘッド優位になりやすい)。 + +### 症状(Larson 2s, 4T, FAST_CAP=0) +- `hak_tiny_free_superslab()` → `ss_remote_push()` → `tiny_publish_notify()` あたりで SIGSEGV。`fault_addr` は常に低い値(例: 0x6261)で、invalid ポインタ参照。 +- Debug Ring で直前イベントを記録すると、「class=4 の remote free → alloc → free_remote → alloc → …」が循環。ptr は SuperSlab 内に見えるが、キューに積まれた時点で破損疑い。 +- FAST_CAP>0 に戻すと crash は発生せず、throughput ≈3.7M ops/s(以前より低下)。publish pipeline がゼロのままのため本質的な性能改善は未着手。 + +### 箱構成と境界 +1. **FrontGate(fast-tier/Hot/TLS)の箱** + - `tiny_fast_pop/push`, `hotmag_pop/push`, TLS SLL/Magazine + - ★ 現在の仮説:FAST_CAP=0 で front が完全にバイパスされる際、remote queue への戻し順序が乱れている +2. **RemoteQueue(ss_remote_push/dain)の箱** + - SuperSlab remote_heads / remote_counts / slab_listed + - `tiny_publish_notify` が通知境界 +3. **Mailbox/Publish の箱** + - `tiny_mailbox_publish/fetch`, `ss_partial_publish/adopt` +4. **Debug Ring(可視化の箱)** + - `HAKMEM_TINY_TRACE_RING=1` で alloc/free/publish 直前イベントをダンプ + +現在の crash は FrontGate または RemoteQueue の内部バグが境界越えで露呈している状態 → 境界を固めて中/外どちらに異常があるか見極める。 + +### 進行中のタスク +1. **RemoteQueue 安全化(優先)** + - [ ] `ss_remote_push()` にポインタ境界チェック(スーパースラブ内か)と Debug Ring ログ追加 + - [ ] remote push 後に `tiny_publish_notify` へ渡すフォーマット(ss+slab_idx)を `tiny_mailbox_publish` で検証用トレース + - [ ] `ss_remote_drain_to_freelist` 前後で freelist pointer をトレースし、破損発生位置を特定(remote queue 内滞留→再 push の経路切り分け) + - [x] `tiny_remote_sentinel_set()` で 0x6261 汚染を一度だけ捕捉し BT + SIGUSR2 を吐くトラップを追加(再現経路の特定用) + - [x] remote side table を箱化 (`1<<20` + `tiny_remote_side_clear()` + `side_overflow` フォールバック) し、飽和時でも sentinel/next の整合を維持 + - [ ] sentinel 汚染が発生した際の callstack を段階別(set/scan/drain)に収集し、同一ノードへの二重 push(実質 double free)発生箇所を pinpoint +2. **FrontGate バイパス検証** + - [ ] `FAST_CAP=0` 時専用のトレースフラグ `HAKMEM_TINY_DEBUG_FAST0=1` で front 層を最小順序に固定(Hot/TLS/Mag を飛ばして直接 remote → TLS リストへ流す) + - [ ] フラグ ON/OFF で crash が消えるか確認 → front 内バグか remote 内バグかを切り分け +3. **再現テスト・フラグ** + - [ ] `scripts/run_larson_defaults.sh tput` に `HAKMEM_TINY_FAST_CAP` オーバーライドを追加(忘れ防止) + - [ ] `scripts/` に crash 再現スクリプト `run_larson_fast0.sh` を用意(2s/4T で SIGSEGV を取得) +4. **publish pipeline 調査(二次優先)** + - crash 解消後に `tiny_publish_notify` の発火率と mailbox drain を再計測し、mmap 偏重の根本原因へ戻る + +### 参考コマンド +- Crash 再現: + `HAKMEM_TINY_FAST_CAP=0 HAKMEM_LARSON_TINY_ONLY=1 HAKMEM_TINY_USE_SUPERSLAB=1 ./larson_hakmem 2 8 128 1024 1 12345 4` +- Debug Ring ダンプ: + `HAKMEM_TINY_TRACE_RING=1 ... ./larson_hakmem ...` + `kill -USR2 ` で途中ダンプ、`SIGSEGV` で最終ダンプ +- publish 通電検証(安全ラン): + `scripts/run_larson_defaults.sh tput 2 4` + + +現状整理(Google系/Larson系ベンチの追い上げフェーズ) + +### 実施済み(即応) +- ベンチ実行時のデバッグ出力によるオーバーヘッドを除去(リリース既定で抑制) + - 変更ファイル: `core/hakmem_tiny_ultra_simple.inc`, `core/hakmem_tiny_metadata.inc` + - 方針: `HAKMEM_DEBUG_VERBOSE` が有効時のみ `fprintf` するようガード + - 効果: ログ出力がボトルネックになるケースを解消(特に tiny/mixed ベンチ) + +- `bench_random_mixed_hakmem` を ULTRA_SIMPLE 版で再ビルド・再計測 + - ビルド: `make bench_random_mixed_hakmem EXTRA_CFLAGS="-DHAKMEM_TINY_PHASE6_ULTRA_SIMPLE=1 -DHAKMEM_BUILD_RELEASE=1"` + - 旧: 23.49 M ops/sec → 新: 25.82 M ops/sec(+9.9%) + +- Larson ベンチ(2秒, 8–128B, chunks=1024, rounds=1, seed=12345)実行 + - system 1T: 14.73 M/s, 4T: 16.76 M/s + - mimalloc 1T: 16.77 M/s, 4T: 16.77 M/s + - HAKMEM 1T: 2.52 M/s, 4T: 4.19 M/s + - `HAKMEM_LARSON_TINY_ONLY=1 HAKMEM_DISABLE_BATCH=1` でも同等(~2.56M / ~4.19M) + +### 観測と仮説(Larson 遅さの主因) +- 既知の分析と一致: 再利用不足 → ページフォールト/ゼロ化増 → sys 時間が支配的 +- Tiny フロントのヒット率が不足(SLL を使うが、Larson パターンで十分に温まらない) +- Metadata 版(Phase 6-1.6)は refill 未対応部分があり現状は封印、ULTRA_SIMPLE で進めるのが安全 + +### 当面の方針(追いつくまでの短期プラン) +1) Larson 用バイナリに ULTRA_SIMPLE を徹底適用してフロントのヒット率を最大化 + - 目標: free/alloc ともに 3–4 命令の経路に乗せる(既に `free()` は alignment-guess 経路有効) + - ビルド: `EXTRA_CFLAGS="-DHAKMEM_TINY_PHASE6_ULTRA_SIMPLE=1 -DHAKMEM_BUILD_RELEASE=1"` + +2) Mixed/Larson の再測定と perf 取得 + - コマンド: `scripts/run_larson.sh -d 2 -t 1,4` + - 詳細: `scripts/run_larson_perf.sh`(PF/IPC/branch/L1d を併記) + +3) 迅速チューニング候補(小粒で効く順) + - Refill 個数の抑制(64→16〜32)で温まり時間短縮、TL-再利用密度を上げ PF を減らす + - SuperSlab サイズの下限を 1MB に固定(`HAKMEM_TINY_SS_MIN_MB=1`)で初期PFを軽減 + - 事前ウォーム(Larson開始前に `sll_refill_small_from_ss()` をサイズ帯毎に数回) + - size→class 変換の LUT 確認(既に O(1) だが、統合経路でも分岐予測を安定化) + +4) 中期(必要なら) + - Dual Free Lists(local/remote 分離)を Tiny に導入(既存の設計を Tiny へ移植) + - Metadata 版の refill 実装を完了(ヘッダ +8B で owner 判定ゼロ化)し安定化後に切替検討 + +### 直近 TODO(実行順) +- [ ] Larson ULTRA_SIMPLE ビルドの固定化(larson_hakmem に EXTRA_CFLAGS 反映) +- [ ] `scripts/run_larson.sh -d 2 -t 1,4` の再実行と結果更新 +- [ ] `scripts/run_larson_perf.sh` で PF/CPU 内訳を取得してボトルネックの再確認 +- [ ] Refill 個数/SSサイズのチューニングで 1T: ~10M, 4T: ~10M の域まで引き上げ + +備考: この更新で、測定時のノイズ(fprintf)は排除済み。以降の差分は純粋にアルゴリズム/チューニング起因として評価可能。 + +--- + +## 🔧 バグ修正と3点セット実装(2025-11-03 09:00) + +結論: 「free 経路の破綻」は修正済み。OOM は設計的な再利用探索の不足が残課題。 + +- 修正 + - ULTRA_SIMPLE free を same-thread のみ直 push に制限。cross-thread free は従来経路へフォールバック。 + - 変更: `core/hakmem.c:820`, `core/hakmem_tiny_ultra_simple.inc:96` + - OOM ワンショット診断(errno/ss_size/alloc_size/RLIMIT/VmSize/RSS/SSカウンタ)を追加。 + - 変更: `core/hakmem_tiny_superslab.c:182` + +- 3点セット(段階導入・既定OFF) + 1) remote queue(cross-thread free 時に per-slab MPSC stack へ) + 2) partial publish/adopt(クラス別公開リング→refill 前に adopt) + 3) adopt 時の remote drain + owner 移譲(best-effort) + - 変更: `core/hakmem_tiny_superslab.h`, `core/hakmem_tiny.c`, `core/hakmem_tiny_free.inc` + - 有効化: `HAKMEM_TINY_SS_ADOPT=1` + +- 観測(Larson, 4T) + - adopt OFF(既定): ~4.19 M/s 安定、稀に ENOMEM 継続 + - adopt ON: OOM は減るがゼロにはならず。1T は低下傾向(~2.3–2.4 M/s)→チューニング要 + +- 次のチューニング(提案) + - `SS_PARTIAL_RING` 長(2/4/8)A/B、adopt 選好(remote多い slab 優先)、採用頻度の抑制(クールダウン) + - perf stat(PF/DTLB)の比較で改善度を定量化 + +使い方(A/B): +``` +# 既定(adopt OFF) +./larson_hakmem 2 8 128 1024 1 12345 4 + +# adopt ON(3点セット有効、A/B計測) +HAKMEM_TINY_SS_ADOPT=1 ./larson_hakmem 2 8 128 1024 1 12345 4 +``` + + +## 🎯 現在のミッション: Phase 6 - Learning-Based Tiny Allocator + +**Status**: ✅ **Phase 6-1 完了!** 🚀🎉 + +**最新アップデート (2025-11-02 18:00):** +- ✅ **Phase 6-1: Ultra-Simple Fast Path 完了!** 🚀🚀🚀 + - **驚異的な結果**: **478.60 M ops/sec** (64B LIFO) + - **System malloc の +174% 高速!** (174.69 M/s → 478.60 M/s) + - **現行 HAKMEM の +777% 高速!** (54.56 M/s → 478.60 M/s) + - **4.17 cycles/op** (理論的最小値に近い) + - **100% hit rate** (10M ops 中 miss 1回のみ) + + - **実装**: "Simple Front + Smart Back" (HAKX Mid-Large +171% の成功パターン適用) + - Fast path: 3-4 命令 (tcache風 pop from free list) + - Backend: Simple mmap-based chunk allocator + - Files: `core/hakmem_tiny_simple.{h,c}` (200行) + + - **なぜこんなに速い?** + 1. Ultra-simple fast path (分岐予測完璧) + 2. Perfect L1 cache locality (TLS array 64B) + 3. Freed blocks 即再利用 (LIFO で 100% hit) + 4. ゼロオーバーヘッド (magazine layers なし) + + - **次のステップ**: + - [ ] Comprehensive benchmark (21 patterns) + - [ ] Memory efficiency 測定 + - [ ] Phase 2: Learning layer 設計 + +--- + +## 📋 過去の試行 (Phase 5以前) + +**Status (旧)**: ✅ Phase 2+1完了 → ❌ Phase 3失敗 → ❌ Phase 4-A1失敗 → ❌ **Phase 5-A失敗** → ❌ **Phase 5-B-Simple 失敗** 💥 + +**最新アップデート (2025-11-02 07:00):** +- ❌ **Phase 5-B-Simple 失敗**: **-71% (ST) / -35% (MT)** 💥💥💥 + - **Single-threaded (bench_tiny_hot, 64B)**: + - System: 169.49 M ops/sec + - HAKMEM Phase 5-B: 49.91 M ops/sec + - **Regression: -71%** (3.4x slower!) + + - **Multi-threaded (bench_mid_large_mt, 8-32KB, 2 threads)**: + - System: 11.51 M ops/sec + - HAKMEM Phase 5-B: 7.44 M ops/sec + - **Regression: -35%** (1.5x slower) + - ⚠️ **NOTE**: Mid/large benchmark tests 8-32KB allocations (outside Tiny range), not directly testing Phase 5-B + + - **根本原因分析**: + 1. **Magazine capacity ミスチューン**: 64 slots は ST workload には小さすぎる + - Batch=100 の場合、2回に1回は slow path に落ちる + - System allocator は tcache (7+ entries per size) で高速 + + 2. **Migration logic オーバーヘッド**: Slow path での free list → Magazine migration が高コスト + - Batch migration (32 items) が頻繁に発生 + - Pointer chase + atomic operations + + 3. **Dual Free Lists の誤算**: ST では効果ゼロ、むしろオーバーヘッド + - ST では remote_free は発生しない + - Dual structures のメモリ overhead のみ + + 4. **Unified Magazine の問題**: 統合で simplicity は得たが performance は失った + - 旧 HotMag (128 slots) + Fast + Quick の組み合わせのほうが高速だった + - 単純化 ≠ 高速化 + + - **教訓**: + - ✅ **Magazine unification 自体は良アイデア** (complexity 削減) + - ❌ **Capacity tuning が不適切** (64 slots → 128+ 必要) + - ❌ **Dual Free Lists は MT 専用** (ST で導入すべきでない) + - ❌ **Migration logic が重すぎる** (batch size 削減 or lazy migration 必要) + + - **次のアクション**: + 1. ⏮️ **Phase 5-B をロールバック** (git revert) + 2. 📊 **Baseline 再測定** (clean state で確認) + 3. 🤔 **Phase 5-B-v2 を検討** (Magazine unification only, Dual Free Lists なし) + 4. 🎯 **Alternative approach**: Phase 6 系 (L25/SuperSlab 最適化) に移行 + +- 🚀 **Phase 5-B-Simple 開始** (2025-11-03 04:00): Dual Free Lists + Magazine統合 🎯 + - **Phase 5-A-v2 をスキップする理由**: + 1. **HAKMEM は既に O(1) size→class 変換** (`g_size_to_class_lut_1k[size]`) + 2. **多層 cache が既に 95%+ hit** → direct cache 追加は効果薄い + 3. **TLS 変数追加で cache pollution** リスク + 4. **期待値**: ±0% (効果とオーバーヘッドが相殺) + - **Phase 5-B-Simple に集中** (期待: +15-23%) + + - **2つの最適化を統合**: + 1. **Dual Free Lists** (mimalloc Phase 5-B): +10-15% + - Local free で atomic 不要 (10+ cycles 削減) + - Cache locality 向上 + 2. **Magazine 統合** (シンプル化): +3-5% + - 3-4層 → 2層に削減 + - TLS cache line を 3-4本 → 1-2本に削減 + - Branch を 3-4回 → 1回に削減 + + - **実装計画** (3-4 days): + ```c + // === Before (現状: 複雑) === + // Layer 1: HotMag (classes 0-2) + if (g_hotmag_enable) ptr = hotmag_pop(); + + // Layer 2: Hot functions (classes 0-3) + if (g_hot_alloc_fn[cls]) ptr = tiny_hot_pop_classN(); + + // Layer 3: Fast cache + ptr = tiny_fast_pop(cls); + + // Layer 4+: Slow path (single freelist + atomic) + ptr = hak_tiny_alloc_slow(); + + // === After (Phase 5-B-Simple: シンプル) === + // Layer 1: Unified Magazine (統合版) + TinyUnifiedMag* mag = &g_tls_mag[cls]; + if (mag->top > 0) return mag->items[--mag->top]; // ← 1 branch! + + // Layer 2: Slow path (Dual Free Lists) + return tiny_alloc_slow_dual(cls); // ← local_free (no atomic!) + ``` + + - **Dual Free Lists の核心**: + ```c + typedef struct TinySlab { + // Phase 5-B: Dual Free Lists + void* local_free; // Local frees (no atomic!) + _Atomic(void*) thread_free; // Remote frees (atomic) + } TinySlab; + + // Free: Local は atomic なし! + if (pthread_equal(slab->owner_tid, self)) { + *(void**)ptr = slab->local_free; + slab->local_free = ptr; // ← No atomic! 10+ cycles 削減 + } else { + atomic_push(&slab->thread_free, ptr); + } + + // Migration: Batch で効率化 + if (!slab->free && slab->local_free) { + slab->free = slab->local_free; // Pointer swap only + slab->local_free = NULL; + } + ``` + + - **期待効果まとめ**: + | 最適化 | 期待効果 | 累積 M ops/sec | + |--------|---------|----------------| + | Baseline | - | 16.53 | + | Dual Free Lists | +10-15% | 18.2-19.0 | + | Magazine 統合 | +3-5% | 18.7-20.0 | + | Branch 削減 | +2-3% | 19.1-20.6 | + | **合計** | **+15-23%** | **19.1-20.3 M ops/sec** 🎯 | + + - **実装ステップ**: + - **Step 1** (Day 1): Unified Magazine 実装 & benchmark + - **Step 2** (Day 2): Dual Free Lists 追加 (TinySlab 構造変更) + - **Step 3** (Day 3): Free path 書き換え (local_free / thread_free 分離) + - **Step 4** (Day 4): Migration logic & 最終 benchmark + + - **削減される TLS 変数**: + ```c + // Before (~1600 bytes + flags) + __thread TinyHotMag g_tls_hot_mag[8]; // 1024 bytes + __thread void* g_fast_head[8]; // 64 bytes + __thread uint16_t g_fast_count[8]; // 16 bytes + __thread TinyQuickSlot g_tls_quick[8]; // 512 bytes + + // After (2048 bytes のみ) + __thread TinyUnifiedMag g_tls_mag[8]; // 2048 bytes + ``` +- ❌ **Phase 5-A失敗**: **-3~-7.7%** (16.53 → 15.25-16.04 M ops/sec) 💥 + - **実装**: Global `slabs_direct[129]` でO(1) direct page cache + - **結果**: 性能**大幅悪化**(期待+15-20% → 実際-3~-7.7%) + - **根本原因**: **Thread-local vs Global の設計ミス** + ```c + // hakmem_tiny.h - 間違った実装 + typedef struct { + TinySlab* slabs_direct[129]; // ❌ Global = 全threadが共有 + } TinyPool; // g_tiny_pool は global + + // mimalloc - 正しい実装 + typedef struct mi_heap_s { + mi_page_t* pages_free_direct[129]; // ✅ Heap ごと = thread-local + } mi_heap_t; // __thread mi_heap_t* heap + ``` + - **性能悪化の3つの要因**: + 1. **pthread_self() オーバーヘッド**: 毎回 `tiny_self_pt()` syscall (~20-40 cycles) + 2. **Remote slab hit**: Global cache が他 thread の slab を指す + → Owner check → Cache clear → Fallback (無駄な3段階処理) + 3. **余分な分岐**: 既存 fast path の前に新たな条件分岐層を追加 + - **測定結果** (2回計測): + - Run 1: 15.25 M ops/sec (-7.7%) + - Run 2: 16.04 M ops/sec (-3.0%) + - Baseline: 16.53 M ops/sec + - **教訓**: **mimalloc の直訳は危険** - アーキテクチャ差異を理解すべき + - mimalloc: `mi_heap_t` は thread-local → `pages_free_direct` も thread-local + - HAKMEM: `TinyPool g_tiny_pool` は global → `slabs_direct` は全 thread 共有 + - **Revert**: 全ての Phase 5-A 変更を完全に revert (16.33 M ops/sec に復元) + - **次の戦略**: Phase 5-A-v2 で **thread-local slabs_direct** を実装 + ```c + // 次回の正しい実装方針 + __thread TinySlab* g_tls_slabs_direct[129]; // ✅ Thread-local + ``` +- 🎊 **Phase 5完了: 47% Gap の正体を解明!** 🔍 + - **mimalloc 完全分析**: 10,000語超の詳細レポート作成完了 + - **3つのファイル生成**: + - `MIMALLOC_ANALYSIS_REPORT.md` - 詳細技術分析 + - `MIMALLOC_KEY_FINDINGS.md` - 要約版 + - `MIMALLOC_IMPLEMENTATION_ROADMAP.md` - 実装計画 + - **Gap の内訳判明**: + 1. **Direct Page Cache (O(1))**: +15-20% ← **最大のボトルネック!** + 2. **Dual Free Lists**: +10-15% + 3. **Branch Hints + Lazy Updates**: +5-8% + - **重要な発見**: mimalloc も **intrusive linked list** を使用 + → Phase 3/4-A1 の「linked-list は最適」は**正しかった**! + → Gap は**マイクロ最適化**から来る(データ構造選択ではない) + - **Hot Path サイクル比較**: + - mimalloc: ~20 cycles (TLS 2 + O(1) lookup 3 + Pop 5) + - HAKMEM: ~30-35 cycles (TLS 3 + Binary search 5 + Atomic pop 15) + - **差分: 10-15 cycles → 47% の性能差** +- 📋 **次のアクション**: Phase 5-A 実装(Direct Page Cache) + - **期待効果**: +15-20% (16.53 → 19.0-19.8 M ops/sec) + - **Effort**: 1-2 days + - **Risk**: Low +- ❌ **Phase 4-A1失敗**: **-0.24%** (16.53 → 16.49 M ops/sec) 💥 + - 実装: TLS-BUMP即値化(immediate-value hot functions) + - 結果: 性能**悪化**(期待+5-8% → 実際-0.24%) + - 原因: **TLS-BUMPは mixed workloadで機能しない** + ```c + // hakmem_tiny_refill.inc.h:258 + if (meta->freelist != NULL) return NULL; // linear mode only + ``` + - 根本問題: bench_random_mixed (50% alloc, 50% free) では freelist が常に populated + - **TLS-BUMPは monotonic allocationのみ有効**(連続allocのみ) + - 教訓: **混在ワークロードでは linked-list が最適** - 追加層は純粋なオーバーヘッド +- 🚀 **Phase 4戦略決定** (2025-11-02 22:00) + - ChatGPT Pro相談完了:構造的アプローチで+10-25%を狙う + - 核心: 現在の6-7層を**3層に統合**(層あたり2-3ns削減) + - 即効施策: TLS-BUMP即値化(+5-8%期待) ← **失敗!** + - 中期施策: 小マガジン128化(+3-5%)+ 3層リファクタ(+10-15%) + - 長期施策: Mid/Large構造改革(per-core arena + TL-Segment) +- ❌ **Phase 3失敗教訓**: **+0.24% のみ** (16.53 → 16.57 M ops/sec) + - 実装: v3 allocator (magazine-based single-tier) + - 原因: 既存のlinked-listが既にmagazineより最適 + - 教訓: **Linked list > Magazine array**(メモリアクセス少ない) +- ✅ **Phase 2+1完了**: **+1.8% 改善** (16.24 → 16.53 M ops/sec) + - Phase 2: TLS range check実装 (owner_slab高速化) + - Phase 1: free()順序変更 (Tiny → Mid MT) + - 結果: 理論通り動作、軽微な改善 ✅ +- 📋 **次のアクション**: Phase 4-A1実装開始(TLS-BUMP即値化) + +**Phase 2+1の教訓:** +1. ✅ TLS range check + 順序変更は**理論通り動作**(50% overhead削減) +2. ❌ free() overheadが想定より小さかった(実測 ~8% vs 想定 ~21%) +3. 💡 **シングルスレッドでは mutex overhead は誤差レベル** +4. 🎯 さらなる改善には**malloc/alloc側**を攻めるべき(27% overhead!) + +**コスト分析(Phase 2+1):** +``` +Before (Mid → Tiny): + Mid null checks: 4 cycles + Mid mutex (empty): 15 cycles ← 想定より軽い! + Tiny registry: 15 cycles + Total: 34 cycles + +After (Tiny → Mid): + TLS check: 2 cycles + Tiny registry: 15 cycles + Total: 17 cycles + +Savings: 17 cycles (50% 削減) +全体への影響: 17 cycles × (free overhead 8%) ≈ +2% 🎯 +``` + +--- + +## 📊 最新perf分析結果 (2025-11-01) + +### ベンチマーク条件 +- **ワークロード**: bench_random_mixed (8-128B, 16 size classes) +- **パラメータ**: 200K cycles, 400 ws, seed=1 +- **スレッド**: 1 (シングルスレッド) + +### パフォーマンス比較 +| Allocator | Throughput | vs mimalloc | +|-----------|------------|-------------| +| HAKMEM | 16.46 M ops/sec | 68% | +| mimalloc | 24.21 M ops/sec | 100% | + +**Gap: 32% slower** ⚠️ + +### 根本原因: アロケータオーバーヘッド + +| Allocator | Total Overhead | malloc/alloc | free/delete | +|-----------|----------------|--------------|-------------| +| mimalloc | 17% | 7.35% | 9.77% | +| HAKMEM | **49%** | 27% | 21.64% | + +**HAKMEM は 3x のCPUサイクルを消費!** + +--- + +## 🔴 Critical Bottlenecks (優先度順) + +### 1. `free()` の無駄なMid Range チェック (Priority 1) 🔥 + +**問題**: +``` +free(ptr) + ↓ +Lock g_mid_registry mutex (2.29%) ← 全freeでロック! + ↓ +Binary search g_mid_registry (7.08%) ← 8KB-32KB範囲チェック + ↓ +Unlock mutex (3.93%) + ↓ +hak_tiny_owner_slab (4.98%) ← 8B-1KB範囲チェック (本来これだけでOK!) + ↓ +hak_tiny_free ← 実際のfree +``` + +**無駄**: 8-128Bのワークロードなのに、全freeでMid Range (8KB-32KB) をチェック! +- mutex lock/unlock: 6.22% +- mid_lookup: 7.08% +- **合計 13.3% のオーバーヘッド** が不要 + +**修正方針**: +```c +free(ptr) + ↓ +Fast TLS range check (Tiny: 8B-1KB) ← NO MUTEX, 直接チェック + ↓ (hit: ~90% for this workload) + hak_tiny_free + ↓ (miss) +Check Mid registry (with mutex) + ↓ (miss) +Check L25/other +``` + +**期待効果**: **~13% スループット向上** (16.46 → 18.6 M ops/sec) + +--- + +### 2. `hak_tiny_owner_slab` の線形探索 (Priority 2) 🟡 + +**問題**: +- 現在: TLS slab listを線形探索 (3.50% overhead) +- mimalloc: ポインタビットパターンで高速判定 + +**修正方針** (mimalloc-style): +```c +// 1. Alignment check +if ((ptr & (SLAB_SIZE - 1)) != 0) return NULL; // Not slab-aligned + +// 2. TLS range check +if (ptr < tls_heap_start || ptr >= tls_heap_end) return NULL; + +// 3. Direct slab header access +SlabHeader* slab = (SlabHeader*)(ptr & ~(SLAB_SIZE - 1)); +return slab; +``` + +**期待効果**: **~3% スループット向上** (3.50% → ~0.5%) + +--- + +### 3. `hak_tiny_alloc_slow` の複雑なフォールバック (Priority 3) 🟢 + +**問題**: +- 4段階フォールバック: hotmag → TLS list → superslab → magazine +- 各段階でTLS変数アクセス + 分岐 + +**修正方針**: +```c +// 1. TLS magazine (most common) +if (fast cache has space) return pop(); + +// 2. Superslab (if enabled) +if (g_use_superslab && superslab_active) return superslab_alloc(); + +// 3. Central refill +return refill_from_central(); +``` + +**期待効果**: **~2-3% スループット向上** + +--- + +### 4. 関数インライン化 (Priority 4) 🟢 + +**候補**: +- `hak_tiny_alloc` → `malloc` にインライン +- `hak_tiny_owner_slab` → `free` にインライン + +**期待効果**: **~2% スループット向上** + +--- + +## 🎨 Phase 2詳細設計: mimalloc-style Fast Owner Check + +### 現在の問題点 + +**現在の `hak_tiny_owner_slab()` 実装:** +```c +TinySlab* hak_tiny_owner_slab(void* ptr) { + int hash = registry_hash(slab_base); // 関数呼び出し + hash計算 + for (int i = 0; i < SLAB_REGISTRY_MAX_PROBE; i++) // 最大8回ループ + // array access + 比較 + atomic load + if (ptr < start || ptr >= end) return NULL; // range validation +} +``` + +**コスト分析:** +- **Positive lookup** (Tiny allocation): hash + 1-2 probes + range check = **~10-15 cycles** +- **Negative lookup** (非Tiny): hash + 1-2 probes = **~8-10 cycles** ← Phase 1失敗の原因! + +### Phase 2新設計: Ultra-Fast Owner Check + +**目標:** +- **Negative lookup**: **1-2 cycles** (現状: 8-10 cycles) → **~85% 削減** 🎯 +- **Positive lookup**: **5-8 cycles** (現状: 10-15 cycles) → **~40% 削減** + +**実装戦略:** +```c +// Phase 2: Ultra-fast owner check (mimalloc-style) +static inline TinySlab* hak_tiny_owner_slab_fast(void* ptr) { + // Step 1: TLS heap range check (1-2 cycles) ← KEY OPTIMIZATION + // Check if ptr is in this thread's Tiny heap range + if (ptr < g_tls_tiny_min || ptr >= g_tls_tiny_max) { + return NULL; // Outside TLS range → FAST NEGATIVE LOOKUP! ✅ + } + + // Step 2: Slab base calculation (1 cycle) + uintptr_t slab_base = (uintptr_t)ptr & ~(TINY_SLAB_SIZE - 1); + + // Step 3: Registry lookup (2-3 cycles) + // Now only called for pointers IN TLS range (hit rate ~90%) + TinySlab* slab = registry_lookup(slab_base); + if (!slab) return NULL; + + // Step 4: Range validation (1-2 cycles) + if (ptr < slab->base || ptr >= slab->base + TINY_SLAB_SIZE) { + return NULL; + } + + return slab; +} +``` + +**最適化ポイント:** +1. **TLS range check を最初に実行** → Negative lookup を 1-2 cycles に! +2. Registry lookup は TLS range内のみ実行 → Hit rate ~90% +3. 既存のregistry_lookupを活用 → 安全性維持 + +### 実装要件 + +**1. TLS heap range tracking:** +```c +// hakmem_tiny.h +extern __thread void* g_tls_tiny_min; +extern __thread void* g_tls_tiny_max; + +// hakmem_tiny.c +__thread void* g_tls_tiny_min = (void*)UINTPTR_MAX; +__thread void* g_tls_tiny_max = NULL; + +// Update on slab allocation (in allocate_new_slab) +static inline void update_tls_tiny_range(void* slab_base) { + if (slab_base < g_tls_tiny_min) g_tls_tiny_min = slab_base; + void* slab_end = slab_base + TINY_SLAB_SIZE; + if (slab_end > g_tls_tiny_max) g_tls_tiny_max = slab_end; +} +``` + +**2. 既存コードとの互換性:** +- `hak_tiny_owner_slab()` をfast版に置き換え +- Registry lookup機構はそのまま維持 (thread-safe) +- SuperSlabは別途処理(既存通り) + +### 期待効果 + +**Negative lookup高速化 (非Tiny allocations):** +- Before: hash (2-3 cycles) + probe (3-4 cycles) + compare = 8-10 cycles +- After: range check (1-2 cycles) = **~85% 削減** 🔥 + +**Positive lookup高速化 (Tiny allocations):** +- Before: hash + probe + range = 10-15 cycles +- After: range + registry + range = 5-8 cycles = **~40% 削減** + +**Combined with Phase 1 (順序変更):** +``` +Tiny allocations (90%): + Before: Mid mutex (13.6%) + owner_slab (8-10 cycles) + After: owner_slab_fast (1-2 cycles) → Tiny free + Savings: 13.6% × 0.9 = ~12% 🎯 + +Mid MT allocations (10%): + Before: owner_slab (8-10 cycles) + Mid mutex + After: owner_slab_fast (1-2 cycles) + Mid mutex + Savings: 8 cycles × 0.1 = ~0.5% + +Total expected gain: ~12-13% (16.46 → 18.4-18.6 M ops/sec) +``` + +--- + +## 📋 実装タスクリスト + +### ✅ Phase 1: free() チェック順序変更 - **完了 (revert含む)** ✅ + +**実装完了:** +- ✅ 1-1. `hakmem.c` free()のチェック順序を変更 (Tiny→Mid) +- ✅ 1-2. ベンチマーク測定: 16.34 M ops/sec (**-0.73% regression**) +- ✅ 1-3. perf再測定して原因分析: `hak_tiny_owner_slab()` が重い (8-10 cycles) +- ✅ 1-4. Phase 1を revert: 16.24 M ops/sec (baseline復帰) + +**教訓:** `hak_tiny_owner_slab()` が重すぎて全freeに適用不可 → Phase 2で根本解決 + +--- + +### 🚀 Phase 2: owner_slab 高速化 - **実装中** 🚀 + +**目標:** +- Negative lookup: 8-10 cycles → **1-2 cycles** (~85%削減) +- Positive lookup: 10-15 cycles → **5-8 cycles** (~40%削減) +- Phase 2完了後、Phase 1再適用 → **~12-13%向上** + +**タスクリスト:** + +- [ ] 2-1. TLS range tracking変数追加 + - [ ] `hakmem_tiny.h`: extern宣言追加 + - [ ] `hakmem_tiny.c`: TLS変数定義 + - [ ] `allocate_new_slab()`: range更新ロジック追加 + +- [ ] 2-2. `hak_tiny_owner_slab_fast()` 実装 + - [ ] TLS range check (negative lookup高速化) + - [ ] Registry lookup (既存機構活用) + - [ ] Range validation (safety確保) + +- [ ] 2-3. 既存 `hak_tiny_owner_slab()` を置き換え + - [ ] 関数名変更 or 実装差し替え + - [ ] 全呼び出し箇所で動作確認 + +- [ ] 2-4. ベンチマーク測定 + - [ ] `./bench_random_mixed_hakmem 200000 400 1` + - [ ] 目標: ~16.24 M ops/sec (変化なし or 微増) + - [ ] 理由: owner_slab単体では効果小、Phase 1再適用で効果発揮 + +- [ ] 2-5. Phase 1再適用 (順序変更) + - [ ] `hakmem.c` free(): Tiny → Mid に変更 + - [ ] ベンチマーク測定 + - [ ] 目標: **18.4-18.6 M ops/sec (+12-13%)** 🎯 + +### 🎯 Phase 3: Allocation Hot Path簡素化 - **設計完了** 📐 + +**Status**: Phase 2+1完了 → Phase 3設計完了 (2025-11-01 22:00) + +**目標**: malloc/allocのoverhead削減 (27% → 20%) +- **期待効果**: +5-10% (16.53 → 17.5-18.0 M ops/sec) +- **アプローチ**: mimalloc-style single-tier hot path + +**現状分析 (perf):** +``` +malloc/alloc overhead: 27% + - hak_tiny_alloc_slow: 9.33% (複雑なフォールバック) + - hak_tiny_alloc: 7.12% (hot path) + - malloc wrapper: 3.67% + - その他: ~7% + +問題点: + ❌ 6+段階のフォールバックチェーン + ❌ 多数のTLS変数アクセス + ❌ Heavy stack frame (14.05% in prologue!) + ❌ Branch misprediction +``` + +**現在のフローチャート:** +```c +hak_tiny_alloc(size) + ↓ +1. size → class_idx + ↓ +2. ifdef BENCH_FASTPATH: + - SLL head check + - TLS Magazine check + - SLL refill + ↓ +3. HotMag front (class <= 2) + ↓ +4. Hot alloc functions (class 0-3) + ↓ +5. tiny_fast_pop() + ↓ +6. hak_tiny_alloc_slow() ← 9.33% overhead! + - HotMag refill + - TLS list refill + - SuperSlab fallback +``` + +**Phase 3新設計 (mimalloc-style):** +```c +void* hak_tiny_alloc_v3(size_t size) { + // 1. Size → class (branchless) + int class_idx = hak_tiny_size_to_class(size); + if (__builtin_expect(class_idx < 0, 0)) return NULL; + + // 2. Single-tier TLS magazine (HOT PATH - 2-3 cycles) + TinyTLSMag* mag = &g_tls_mags[class_idx]; + int top = mag->top; + if (__builtin_expect(top > 0, 1)) { + void* ptr = mag->items[--top].ptr; + mag->top = top; + return ptr; // ← 最速パス! 🚀 + } + + // 3. Refill + fallback (cold path) + return hak_tiny_alloc_slow_v3(size, class_idx); +} + +static void* __attribute__((cold, noinline)) +hak_tiny_alloc_slow_v3(size_t size, int class_idx) { + TinyTLSMag* mag = &g_tls_mags[class_idx]; + + // Step 1: Try refilling magazine from SuperSlab + if (mag_refill_from_superslab(class_idx, mag, 32) > 0) { + return mag->items[--mag->top].ptr; + } + + // Step 2: Allocate new SuperSlab + return hak_tiny_alloc_superslab(class_idx); +} +``` + +**削減内容:** +``` +Branches: 6+ → 2 +TLS変数: 多数 → 1つ (mag) +Stack frame: Heavy → Minimal (inline候補) +Hot path cycles: ~20-30 → ~5-8 (60-70%削減) 🎯 +``` + +**タスクリスト:** + +- [ ] 3-1. `hak_tiny_alloc_v3()` 実装 + - [ ] Single-tier magazine hot path + - [ ] Branchless size-to-class + - [ ] Minimal stack frame + +- [ ] 3-2. `hak_tiny_alloc_slow_v3()` 簡素化 + - [ ] 2-tier fallback (magazine refill → superslab) + - [ ] Remove HotMag/TLS list/fast_pop complexity + - [ ] `__attribute__((cold, noinline))` + +- [ ] 3-3. Magazine refill最適化 + - [ ] `mag_refill_from_superslab()` 専用関数 + - [ ] Batch refill (32-64 items) + - [ ] Zero overhead on hit + +- [ ] 3-4. ベンチマーク測定 + - [ ] `./bench_random_mixed_hakmem 200000 400 1` + - [ ] 目標: **17.5-18.0 M ops/sec (+5-10%)** 🎯 + - [ ] vs mimalloc: 72-74% + +--- + +### Phase 4: 関数インライン化 (Phase 3成功後) + +**Phase 4: インライン化** +- `hak_tiny_alloc_v3` → `malloc` (force inline) +- `hak_tiny_free` → `free` (force inline) +- 目標: +2-3% (18.0-19.0 M ops/sec) + +--- + +## 🎯 マイルストーン + +| Phase | Target Throughput | Actual Result | vs mimalloc | Status | +|-------|-------------------|---------------|-------------|--------| +| Baseline | 16.24 M ops/sec | 16.24 M ops/sec | 67% | ✅ Baseline | +| ~~Phase 1 (単体)~~ | ~~18.6 M ops/sec~~ | ~~16.34 M ops/sec~~ | ~~67%~~ | ❌ **FAILED** (-0.73%) | +| Phase 2 (単体) | ~16.2 M ops/sec | 15.70 M ops/sec | 65% | ✅ **完了** (-3.3%, 予想通り) | +| **Phase 2+1 Combined** | ~~18.4-18.6 M ops/sec~~ | **16.53 M ops/sec** | **68%** | ✅ **完了** (+1.8%) | +| ~~Phase 3 (v3 alloc)~~ | ~~17.5-18.0 M ops/sec~~ | ~~16.57 M ops/sec~~ | ~~68%~~ | ❌ **FAILED** (+0.24%) | +| Phase 4 (インライン化) | 18.0-19.0 M ops/sec | - | 74-79% | ⏳ TODO | +| **Ultimate Goal** | **22-24 M ops/sec** | - | **90-100%** | 🌟 Long-term Target | + +**Phase 2+1の洞察 (2025-11-01 21:30):** +- ✅ Phase 2+1は**理論通り動作** (+1.8% 改善) +- ❌ 期待値(+12-13%)との乖離は**Mid MT mutexコスト見積もり誤り**が原因 +- 💡 シングルスレッド、空registryでは mutex は超軽量(~15 cycles) +- 🎯 **次の主戦場**: malloc/alloc側の27% overheadを攻める! + +--- + +## 📁 関連ドキュメント + +- ✅ **perf分析レポート**: `docs/PERF_ANALYSIS_TINY_MIXED.md` + - 詳細なボトルネック分析 + - perf annotate結果 + - 最適化ロードマップ + +- 📊 **ベンチマーク比較**: 他のAIちゃんのレビュー + - Mixed: HAKMEM 66.7% of mimalloc (weak) + - Mid MT: HAKMEM 129.4% of mimalloc (strong) + +--- + +## 💡 Next Action (今すぐやること) + +### 最初のステップ: free() Fast TLS Check実装 + +```bash +# 1. hakmem.c のfree()を開いて、Fast TLS checkを追加 +vim core/hakmem.c + +# 2. 変更箇所 +# free() の冒頭 (guard check後) に: +# - TLS Tiny heap範囲チェック +# - Hit時は直接 hak_tiny_free() +# - Miss時は既存フロー + +# 3. ビルド & ベンチマーク +make bench_random_mixed_hakmem +./bench_random_mixed_hakmem 200000 400 1 + +# 4. perf確認 +perf record -F 999 -g ./bench_random_mixed_hakmem 200000 400 1 +perf report --stdio -n --percent-limit 1 +``` + +--- + +## 🔥 モチベーション + +**目標**: Tiny Mixed Workloadで mimalloc に匹敵する性能 + +**現状**: +- Mid MT: **138% of mimalloc** ✅ (Already winning!) +- Tiny Mixed: **68% of mimalloc** ⚠️ (Need improvement!) + +**今日の目標**: +- Phase 1実装で **77% of mimalloc** (16.46 → 18.6 M ops/sec) + +**Let's optimize! 🚀** + +--- + +## 🚀 Phase 4: 構造的最適化戦略(2025-11-02策定) + +### 戦略概要 + +**目標**: mimalloc比90-100%到達(現状68% → 22-24 M ops/sec) + +**核心アプローチ**: +1. **3層への統合**(現在の6-7層を削減) +2. **TLS-BUMP即値化**(hot-class専用最適化) +3. **段階的実装**(即効 → 中期 → 長期) + +### Phase 4-A: 即効施策(今週実装) + +#### A-1: TLS-BUMP即値化 + hot-class強化 ⚡ +**期待効果**: +5-8%(16.53 → 17.5-18.0 M ops/sec) + +**実装内容**: +- `g_ultra_bump_shadow`有効化(現在無効) +- hot-class(16/32/64B)を即値化関数に書き換え +- 分岐ゼロ化(cmov版も検討) + +**変更ファイル**: +- `core/hakmem_tiny_hot_pop.inc.h` - 即値化版関数 +- `core/hakmem_tiny.c` - BUMP有効化 +- `core/hakmem_tiny_config.c` - デフォルト設定 + +**コード例**: +```c +// Before (現在) +void* head = g_fast_head[class_idx]; +if (head) { + g_fast_head[class_idx] = *(void**)head; + return head; +} + +// After (A-1) +uint8_t* p = g_tls_bcur[0]; +uint8_t* n = p + 16; // ← 即値! +if (likely(n <= g_tls_bend[0])) { + g_tls_bcur[0] = n; + return p; // ← 分岐1つ、TLS書き込み1回 +} +return tiny_bump_refill_cold(0); // noinline +``` + +#### A-2: 小マガジン容量最適化 📦 +**期待効果**: +3-5% + +**実装内容**: +- class 0-2(8/16/32B): 128エントリ固定 +- class 3+(64B+): 64エントリ(現状維持) +- L1ヒット率向上、ワーキングセット最適化 + +**変更ファイル**: +- `core/hakmem_tiny_magazine.h` - 容量定数 + +### Phase 4-B: 中期施策(1-2週間) + +#### B-1: 3層への統合リファクタリング 🏗️ +**期待効果**: +10-15%(累積+20-25%) + +**実装内容**: +```c +void* hak_tiny_alloc_v4(size_t size) { + int k = hak_tiny_size_to_class(size); + if (unlikely(k < 0)) return NULL; + + // Layer 1: TLS-BUMP (hot-class専用、即値化) + if (g_hot_alloc_fn[k]) { + void* p = g_hot_alloc_fn[k](); + if (likely(p)) return p; + } + + // Layer 2: 小マガジン128 + TinyTLSMag* mag = &g_tls_mags[k]; + if (likely(mag->top > 0)) { + return mag->items[--mag->top].ptr; + } + + // Layer 3: Slow (全部noinline/cold) + return hak_tiny_alloc_slow_v4(size, k); +} +``` + +**削減対象**: +- ❌ HAKMEM_TINY_BENCH_FASTPATH(SLL系) +- ❌ TinyHotMag複数層 +- ❌ wrapper context handling(slow pathへ) +- ❌ 過剰なTLS変数(bcur/bendのみ保持) + +#### B-2: ACE簡素化(4ノブ×4状態) +**期待効果**: p95安定化、ホットパス干渉除去 + +**実装内容**: +- ノブ4つ: BATCH, HOT_THRESHOLD, drain_mask, slab_lg +- 状態4つ: STEADY, BURST, REMOTE_HEAVY, MEM_TIGHT +- tick=150ms、観測は1/16Kサンプル +- ホットパス完全非干渉 + +### Phase 4-C: 長期施策(2-4週間) + +#### C-1: Mid/Large TL-Segment +**期待効果**: Mid/Large単スレで2×改善 + +**実装内容**: +- 4-16KBページ単位のTLバンプ +- ページ内free-list(連結生成最小化) +- ≥64KBは直map再利用キャッシュ(LRU 64本) + +#### C-2: per-core arena + SPSC remote queue +**期待効果**: MT競合削減、2-3×改善 + +**実装内容**: +- スレッド→core固定 +- cross-thread freeはSPSCリング +- allocのついでにdrain(バッチ128-256) +- レジストリcoreシャード化 + +### マイルストーン更新 + +| Phase | Target | Expected Result | vs mimalloc | Status | +|-------|--------|-----------------|-------------|---------| +| Baseline | 16.24 M | 16.24 M | 67% | ✅ Baseline | +| Phase 2+1 | 18.4-18.6 M | **16.53 M** | **68%** | ✅ 完了 (+1.8%) | +| ~~Phase 3~~ | ~~17.5-18.0 M~~ | ~~16.57 M~~ | ~~68%~~ | ❌ 失敗 (+0.24%) | +| **Phase 4-A** | **17.5-18.5 M** | - | **72-76%** | ⏳ **実装中** | +| Phase 4-B | 19.0-20.0 M | - | 78-83% | 📋 設計完了 | +| Phase 4-C | 22-24 M | - | 90-100% | 📐 構想中 | + +### 48時間ロードマップ + +**Day 1 (今日)**: +1. ✅ ChatGPT Pro相談完了 +2. ✅ ドキュメント更新 +3. 🔧 Phase 4-A1実装(TLS-BUMP即値化) +4. 🔧 ビルド & ベンチマーク + +**Day 2 (明日)**: +1. 📊 Phase 4-A1結果分析 +2. 🔧 Phase 4-A2実装(小マガジン128) +3. 📐 Phase 4-B詳細設計 +4. 🚀 Phase 4-B実装開始判断 + +### 設計原則 + +1. **2レジスタ経路死守**: `bcur/bend`だけでalloc完結 +2. **層は最小3段**: `TLS-BUMP → 小マガジン → Slow` +3. **ホット/コールド完全分離**: データもコードも64B境界分離 +4. **統計はサンプルのみ**: 1/16384、ホットパスに書き込みなし +5. **ヘッダ非更新**: slowで同期、allocはTLSのみ + +### 受け入れ基準 + +- Tiny-Hot 32/64/128B: **mimalloc ≥90-110%** +- Random Mixed: **mimalloc ≥90-105%**(p95安定) +- Mid/Large単: **≥80-100%**(段階的) +- Mid/Large MT: **×2改善** → 20-30%差まで短縮 +- RSS: **予算±10%内**、MEM_TIGHTで守る + +--- + +--- + +## 🚀 Phase 5: mimalloc 分析 & Direct Page Cache 実装 + +### Phase 5 Overview + +**mimalloc 分析完了 (2025-11-03):** +- 47% Gap の根本原因を特定 +- 3つの詳細レポート作成 +- Phase 3/4-A1 の教訓を確認: linked-list は最適 + +**Key Findings:** +1. **Direct Page Cache (O(1))** が最大のボトルネック: +15-20% +2. mimalloc も intrusive linked list を使用(Phase 3 の結論は正しい) +3. Gap はマイクロ最適化から来る(データ構造選択ではない) + +--- + +### Phase 5-A: Direct Page Cache 実装 + +**Goal:** サイズ→ページ lookup を O(log n) から O(1) に + +**Current (HAKMEM):** +```c +// Binary search through size classes - O(log n) +int class_idx = hak_tiny_size_to_class(size); // 3-5 comparisons +TinySlab* slab = g_tiny_pool.free_slabs[class_idx]; +``` + +**Target (mimalloc-style):** +```c +// Direct array index - O(1) +TinySlab* slab = g_tiny_pool.slabs_direct[size >> 3]; // 1 cycle! +``` + +**Implementation Plan:** + +**5-A-1. データ構造拡張** +```c +// core/hakmem_tiny.h +typedef struct { + TinySlab* free_slabs[TINY_NUM_CLASSES]; // Existing + TinySlab* full_slabs[TINY_NUM_CLASSES]; // Existing + TinySlab* slabs_direct[129]; // NEW: Direct cache (8-1024B) + // ... existing fields +} TinyPool; +``` + +**5-A-2. Direct cache の更新** +```c +// Slab allocation時に direct cache を populate +static TinySlab* allocate_new_slab(int class_idx) { + TinySlab* slab = /* ... existing allocation ... */; + + // NEW: Populate direct cache for this size class + size_t block_size = g_tiny_class_sizes[class_idx]; + for (size_t sz = block_size; sz < block_size + 8 && sz <= 1024; sz++) { + int idx = sz >> 3; // size / 8 + if (g_tiny_pool.slabs_direct[idx] == NULL) { + g_tiny_pool.slabs_direct[idx] = slab; + } + } + + return slab; +} + +// Slab exhaustion時に direct cache をクリア +static void move_to_full_list(int class_idx, TinySlab* slab) { + /* ... existing code ... */ + + // NEW: Clear direct cache entries pointing to this slab + for (int i = 0; i < 129; i++) { + if (g_tiny_pool.slabs_direct[i] == slab) { + g_tiny_pool.slabs_direct[i] = NULL; + } + } +} +``` + +**5-A-3. Hot path 書き換え** +```c +void* hak_tiny_alloc(size_t size) { + if (size > 1024 || size == 0) return NULL; + + // NEW: O(1) direct cache lookup + int idx = size >> 3; // size / 8 + TinySlab* slab = g_tiny_pool.slabs_direct[idx]; + + if (__builtin_expect(slab != NULL, 1)) { + // Fast path: direct cache hit + void* ptr = pop_from_slab(slab); + if (__builtin_expect(ptr != NULL, 1)) { + return ptr; // ← 5 cycles saved! + } + // Slab exhausted, clear cache and fallback + g_tiny_pool.slabs_direct[idx] = NULL; + } + + // Slow path: fallback to existing binary search + int class_idx = hak_tiny_size_to_class(size); + return hak_tiny_alloc_slow(class_idx); +} +``` + +**Expected Results:** +- **Cycle reduction:** 5 cycles per allocation (binary search elimination) +- **Throughput:** +15-20% (16.53 → 19.0-19.8 M ops/sec) +- **Memory overhead:** 1032 bytes (129 pointers) +- **Risk:** Low (fallback to existing path on miss) + +**Timeline:** 1-2 days + +--- + +### Phase 5-B: Dual Free Lists (Next) + +**Goal:** Local/Remote free list 分離で atomic ops 削減 + +**Expected Results:** +10-15% additional + +--- + +### Phase 5-C: Branch Hints + Flags (Next) + +**Goal:** Predictable branch + bit-packed flags + +**Expected Results:** +5-8% additional + +--- + +## 📊 Phase 5 Roadmap + +| Phase | Impact | Effort | Risk | Status | +|-------|--------|--------|------|--------| +| 5-A: Direct Cache | +15-20% | 1-2d | Low | 🔜 Next | +| 5-B: Dual Free Lists | +10-15% | 3-5d | Med | ⏳ Pending | +| 5-C: Branch Hints | +5-8% | 1-2d | Low | ⏳ Pending | +| **Total** | **+45%** | **1-2w** | **Low** | **16.53 → 24.0 M ops/sec** | + + +--- + +## 🚀 Phase 6: Learning-Based Tiny Allocator (2025-11-02~) + +### 戦略: "Simple Front + Smart Back" (Mid-Large HAKX の真似) + +**背景:** +- Phase 5-B 失敗: Magazine unification で -71% 💀 +- 包括的ベンチマークで根本原因特定: **Fast path が複雑すぎる** +- Mid-Large HAKX は +171% で成功 → 同じアプローチを Tiny に適用 + +### 目標 +- **Phase 1 (1週間)**: Ultra-Simple Fast Path → System の 70-80% (95-108 M ops/sec) +- **Phase 2 (1週間)**: 学習層追加 → System の 80-90% (108-122 M ops/sec) +- **Phase 3 (1週間)**: メモリ効率最適化 → System 同等速度 + メモリで勝つ 🏆 + +### 設計コンセプト + +#### Front: Ultra-Simple (System tcache 風) +```c +void* hak_tiny_alloc(size_t size) { + int cls = size_to_class_inline(size); + void** head = &g_tls_cache[cls]; + void* ptr = *head; + if (ptr) { + *head = *(void**)ptr; // 3-4 命令のみ! + return ptr; + } + return hak_tiny_alloc_slow_adaptive(size, cls); +} +``` + +#### Back: Smart (学習層) +- **Class Hotness Tracking**: どのサイズが hot/cold か学習 +- **動的キャッシュ容量調整**: Hot → 256 slots, Cold → 16 slots +- **Adaptive Refill Count**: Miss rate に応じて 16-128 blocks + +### Phase 1: Ultra-Simple Fast Path (進行中) + +**実装内容:** +1. `core/hakmem_tiny_simple.c` 新規作成 +2. TLS Free List ベースの fast path (3-4 命令) +3. SuperSlab からの refill (既存を流用) + +**ファイル:** +- `core/hakmem_tiny_simple.c` - シンプル版 Tiny allocator +- `core/hakmem_tiny_simple.h` - ヘッダ + +**ベンチマーク:** +- `bench_tiny_hot` で測定 +- 目標: System の 70-80% + +### 成功の鍵 + +1. **Fast path を System tcache と同等に** (3-4 命令) +2. **学習層で差別化** (動的容量調整) +3. **Mid-Large の成功パターンを適用** (+171% の実績) + +### 関連ドキュメント +- [`benchmarks/results/TINY_PERFORMANCE_ANALYSIS.md`](benchmarks/results/TINY_PERFORMANCE_ANALYSIS.md) - 根本原因分析 +- [`benchmarks/results/BENCHMARK_SUMMARY_2025_11_02.md`](benchmarks/results/BENCHMARK_SUMMARY_2025_11_02.md) - 包括的ベンチマーク結果 diff --git a/core/box/free_local_box.c b/core/box/free_local_box.c index b9a754d9..f72cd613 100644 --- a/core/box/free_local_box.c +++ b/core/box/free_local_box.c @@ -26,8 +26,54 @@ void tiny_free_local_box(SuperSlab* ss, int slab_idx, TinySlabMeta* meta, void* } void* prev = meta->freelist; + + // FREELIST CORRUPTION DEBUG: Validate pointer before writing + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + size_t blk = g_tiny_class_sizes[ss->size_class]; + uint8_t* base_ss = (uint8_t*)ss; + uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx); + + // Verify prev pointer is valid (if not NULL) + if (prev != NULL) { + uintptr_t prev_addr = (uintptr_t)prev; + uintptr_t slab_addr = (uintptr_t)slab_base; + + // Check if prev is within this slab + if (prev_addr < (uintptr_t)base_ss || prev_addr >= (uintptr_t)base_ss + (2*1024*1024)) { + fprintf(stderr, "[FREE_CORRUPT] prev=%p outside SuperSlab ss=%p (cls=%u slab=%d)\n", + prev, ss, ss->size_class, slab_idx); + tiny_failfast_abort_ptr("free_local_prev_range", ss, slab_idx, ptr, "prev_outside_ss"); + } + + // Check alignment of prev + if ((prev_addr - slab_addr) % blk != 0) { + fprintf(stderr, "[FREE_CORRUPT] prev=%p misaligned (cls=%u slab=%d blk=%zu offset=%zu)\n", + prev, ss->size_class, slab_idx, blk, (size_t)(prev_addr - slab_addr)); + fprintf(stderr, "[FREE_CORRUPT] Writing from ptr=%p, freelist was=%p\n", ptr, prev); + tiny_failfast_abort_ptr("free_local_prev_misalign", ss, slab_idx, ptr, "prev_misaligned"); + } + } + + fprintf(stderr, "[FREE_VERIFY] cls=%u slab=%d ptr=%p prev=%p (offset_ptr=%zu offset_prev=%zu)\n", + ss->size_class, slab_idx, ptr, prev, + (size_t)((uintptr_t)ptr - (uintptr_t)slab_base), + prev ? (size_t)((uintptr_t)prev - (uintptr_t)slab_base) : 0); + } + *(void**)ptr = prev; meta->freelist = ptr; + + // FREELIST CORRUPTION DEBUG: Verify write succeeded + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + void* readback = *(void**)ptr; + if (readback != prev) { + fprintf(stderr, "[FREE_CORRUPT] Wrote prev=%p to ptr=%p but read back %p!\n", + prev, ptr, readback); + fprintf(stderr, "[FREE_CORRUPT] Memory corruption detected during freelist push\n"); + tiny_failfast_abort_ptr("free_local_readback", ss, slab_idx, ptr, "write_corrupted"); + } + } + tiny_failfast_log("free_local_box", ss->size_class, ss, meta, ptr, prev); // BUGFIX: Memory barrier to ensure freelist visibility before used decrement // Without this, other threads can see new freelist but old used count (race) diff --git a/core/hakmem_tiny_free.inc b/core/hakmem_tiny_free.inc index 3bc795ae..fe2bfdb0 100644 --- a/core/hakmem_tiny_free.inc +++ b/core/hakmem_tiny_free.inc @@ -33,6 +33,35 @@ static inline void tiny_drain_freelist_to_sll_once(SuperSlab* ss, int slab_idx, int moved = 0; while (m->freelist && moved < budget) { void* p = m->freelist; + + // CORRUPTION DEBUG: Validate freelist pointer before moving to TLS SLL + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + extern const size_t g_tiny_class_sizes[]; + size_t blk = g_tiny_class_sizes[class_idx]; + void* old_head = g_tls_sll_head[class_idx]; + + // Validate p alignment + if (((uintptr_t)p % blk) != 0) { + fprintf(stderr, "[DRAIN_CORRUPT] Freelist ptr=%p misaligned (cls=%d blk=%zu offset=%zu)\n", + p, class_idx, blk, (uintptr_t)p % blk); + fprintf(stderr, "[DRAIN_CORRUPT] Attempting to drain corrupted freelist to TLS SLL!\n"); + fprintf(stderr, "[DRAIN_CORRUPT] ss=%p slab=%d moved=%d/%d\n", ss, slab_idx, moved, budget); + abort(); + } + + // Validate old_head alignment if not NULL + if (old_head && ((uintptr_t)old_head % blk) != 0) { + fprintf(stderr, "[DRAIN_CORRUPT] TLS SLL head=%p already corrupted! (cls=%d blk=%zu offset=%zu)\n", + old_head, class_idx, blk, (uintptr_t)old_head % blk); + fprintf(stderr, "[DRAIN_CORRUPT] Corruption detected BEFORE drain write (ptr=%p)\n", p); + fprintf(stderr, "[DRAIN_CORRUPT] ss=%p slab=%d moved=%d/%d\n", ss, slab_idx, moved, budget); + abort(); + } + + fprintf(stderr, "[DRAIN_TO_SLL] cls=%d ptr=%p old_head=%p moved=%d/%d\n", + class_idx, p, old_head, moved, budget); + } + m->freelist = *(void**)p; *(void**)p = g_tls_sll_head[class_idx]; g_tls_sll_head[class_idx] = p; @@ -239,6 +268,32 @@ void hak_tiny_free(void* ptr) { if (class_idx >= 0 && class_idx <= 3) { uint32_t sll_cap = sll_cap_for_class(class_idx, (uint32_t)TINY_TLS_MAG_CAP); if ((int)g_tls_sll_count[class_idx] < (int)sll_cap) { + // CORRUPTION DEBUG: Validate ptr and head before TLS SLL write + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + extern const size_t g_tiny_class_sizes[]; + size_t blk = g_tiny_class_sizes[class_idx]; + void* old_head = g_tls_sll_head[class_idx]; + + // Validate ptr alignment + if (((uintptr_t)ptr % blk) != 0) { + fprintf(stderr, "[FAST_FREE_CORRUPT] ptr=%p misaligned (cls=%d blk=%zu offset=%zu)\n", + ptr, class_idx, blk, (uintptr_t)ptr % blk); + fprintf(stderr, "[FAST_FREE_CORRUPT] Attempting to push corrupted pointer to TLS SLL!\n"); + abort(); + } + + // Validate old_head alignment if not NULL + if (old_head && ((uintptr_t)old_head % blk) != 0) { + fprintf(stderr, "[FAST_FREE_CORRUPT] TLS SLL head=%p already corrupted! (cls=%d blk=%zu offset=%zu)\n", + old_head, class_idx, blk, (uintptr_t)old_head % blk); + fprintf(stderr, "[FAST_FREE_CORRUPT] Corruption detected BEFORE fast free write (ptr=%p)\n", ptr); + abort(); + } + + fprintf(stderr, "[FAST_FREE] cls=%d ptr=%p old_head=%p count=%u\n", + class_idx, ptr, old_head, g_tls_sll_count[class_idx]); + } + *(void**)ptr = g_tls_sll_head[class_idx]; g_tls_sll_head[class_idx] = ptr; g_tls_sll_count[class_idx]++; @@ -263,9 +318,47 @@ void hak_tiny_free(void* ptr) { // Ultra free: push directly to TLS SLL without magazine init int sll_cap = ultra_sll_cap_for_class(class_idx); if ((int)g_tls_sll_count[class_idx] < sll_cap) { + // CORRUPTION DEBUG: Validate ptr and head before TLS SLL write + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + extern const size_t g_tiny_class_sizes[]; + size_t blk = g_tiny_class_sizes[class_idx]; + void* old_head = g_tls_sll_head[class_idx]; + + // Validate ptr alignment + if (((uintptr_t)ptr % blk) != 0) { + fprintf(stderr, "[ULTRA_FREE_CORRUPT] ptr=%p misaligned (cls=%d blk=%zu offset=%zu)\n", + ptr, class_idx, blk, (uintptr_t)ptr % blk); + fprintf(stderr, "[ULTRA_FREE_CORRUPT] Attempting to push corrupted pointer to TLS SLL!\n"); + abort(); + } + + // Validate old_head alignment if not NULL + if (old_head && ((uintptr_t)old_head % blk) != 0) { + fprintf(stderr, "[ULTRA_FREE_CORRUPT] TLS SLL head=%p already corrupted! (cls=%d blk=%zu offset=%zu)\n", + old_head, class_idx, blk, (uintptr_t)old_head % blk); + fprintf(stderr, "[ULTRA_FREE_CORRUPT] Corruption detected BEFORE ultra free write (ptr=%p)\n", ptr); + abort(); + } + + fprintf(stderr, "[ULTRA_FREE] cls=%d ptr=%p old_head=%p count=%u\n", + class_idx, ptr, old_head, g_tls_sll_count[class_idx]); + } + *(void**)ptr = g_tls_sll_head[class_idx]; g_tls_sll_head[class_idx] = ptr; g_tls_sll_count[class_idx]++; + + // CORRUPTION DEBUG: Verify write succeeded + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + void* readback = *(void**)ptr; + void* new_head = g_tls_sll_head[class_idx]; + if (readback != *(void**)&readback || new_head != ptr) { + fprintf(stderr, "[ULTRA_FREE_CORRUPT] Write verification failed! ptr=%p new_head=%p\n", + ptr, new_head); + abort(); + } + } + return; } } diff --git a/core/hakmem_tiny_refill_p0.inc.h b/core/hakmem_tiny_refill_p0.inc.h index 29f2a4d5..edf90e72 100644 --- a/core/hakmem_tiny_refill_p0.inc.h +++ b/core/hakmem_tiny_refill_p0.inc.h @@ -94,11 +94,17 @@ static inline int sll_refill_batch_from_ss(int class_idx, int max_take) { // === P0 Batch Carving Loop === while (want > 0) { + // Calculate slab base for validation (accounts for 2048 offset in slab 0) uintptr_t ss_base = 0; uintptr_t ss_limit = 0; - if (tls->ss) { - ss_base = (uintptr_t)tls->ss; - ss_limit = ss_base + ((size_t)1ULL << tls->ss->lg_size); + if (tls->ss && tls->slab_idx >= 0) { + uint8_t* slab_base = tiny_slab_base_for(tls->ss, tls->slab_idx); + ss_base = (uintptr_t)slab_base; + // Limit is end of current slab + ss_limit = ss_base + SLAB_SIZE; + if (tls->slab_idx == 0) { + ss_limit = ss_base + (SLAB_SIZE - SUPERSLAB_SLAB0_DATA_OFFSET); + } } // Handle freelist items first (usually 0) TinyRefillChain chain; @@ -132,6 +138,17 @@ static inline int sll_refill_batch_from_ss(int class_idx, int max_take) { // Get slab base uint8_t* slab_base = tls->slab_base ? tls->slab_base : tiny_slab_base_for(tls->ss, tls->slab_idx); + + // Diagnostic log (one-shot) + static _Atomic int g_carve_log_printed = 0; + if (atomic_load(&g_carve_log_printed) == 0 && + atomic_exchange(&g_carve_log_printed, 1) == 0) { + fprintf(stderr, "[BATCH_CARVE] cls=%u slab=%d used=%u cap=%u batch=%u base=%p bs=%zu\n", + class_idx, tls->slab_idx, meta->used, meta->capacity, batch, + (void*)slab_base, bs); + fflush(stderr); + } + TinyRefillChain carve; trc_linear_carve(slab_base, bs, meta, batch, &carve); trc_splice_to_sll(class_idx, &carve, &g_tls_sll_head[class_idx], &g_tls_sll_count[class_idx]); diff --git a/core/hakmem_tiny_superslab.c b/core/hakmem_tiny_superslab.c index 62a181d3..da7e9d33 100644 --- a/core/hakmem_tiny_superslab.c +++ b/core/hakmem_tiny_superslab.c @@ -547,6 +547,21 @@ void superslab_init_slab(SuperSlab* ss, int slab_idx, size_t block_size, uint32_ size_t usable_size = (slab_idx == 0) ? SUPERSLAB_SLAB0_USABLE_SIZE : SUPERSLAB_SLAB_USABLE_SIZE; int capacity = (int)(usable_size / block_size); + // Diagnostic: Verify capacity for class 7 slab 0 (one-shot) + if (ss->size_class == 7 && slab_idx == 0) { + static _Atomic int g_cap_log_printed = 0; + if (atomic_load(&g_cap_log_printed) == 0 && + atomic_exchange(&g_cap_log_printed, 1) == 0) { + fprintf(stderr, "[SUPERSLAB_INIT] class 7 slab 0: usable_size=%zu block_size=%zu capacity=%d\n", + usable_size, block_size, capacity); + fprintf(stderr, "[SUPERSLAB_INIT] Expected: 63488 / 1024 = 62 blocks\n"); + if (capacity != 62) { + fprintf(stderr, "[SUPERSLAB_INIT] WARNING: capacity=%d (expected 62!)\n", capacity); + } + fflush(stderr); + } + } + // Phase 6.24: Lazy freelist initialization // NO freelist build here! (saves 4000-8000 cycles per slab init) // freelist will be built on-demand when first free() is called @@ -557,7 +572,8 @@ void superslab_init_slab(SuperSlab* ss, int slab_idx, size_t block_size, uint32_ meta->freelist = NULL; // NULL = linear allocation mode meta->used = 0; meta->capacity = (uint16_t)capacity; - meta->owner_tid = owner_tid; + meta->carved = 0; // FIX: Initialize carved counter (monotonic carve progress) + meta->owner_tid = (uint16_t)owner_tid; // FIX: Cast to uint16_t (changed from uint32_t) // Store slab_start in SuperSlab for later use // (We need this for linear allocation) diff --git a/core/superslab/superslab_types.h b/core/superslab/superslab_types.h index c0e88d4d..5959a91b 100644 --- a/core/superslab/superslab_types.h +++ b/core/superslab/superslab_types.h @@ -47,10 +47,12 @@ typedef struct TinySlabMeta { void* freelist; // Freelist head (NULL = linear mode, Phase 6.24) uint16_t used; // Blocks currently used uint16_t capacity; // Total blocks in slab - uint32_t owner_tid; // Owner thread ID (for same-thread fast path) + uint16_t carved; // Blocks carved from linear region (monotonic, never decrements) + uint16_t owner_tid; // Owner thread ID (for same-thread fast path, 16-bit to fit carved) // Phase 6.24: freelist == NULL → linear allocation mode (lazy init) // Linear mode: allocate sequentially without building freelist // Freelist mode: use freelist after first free() call + // FIX: carved prevents double-allocation when used decrements after free } TinySlabMeta; // SuperSlab header (cache-line aligned, 64B) diff --git a/core/tiny_alloc_fast.inc.h b/core/tiny_alloc_fast.inc.h index 543aea5e..8c58bdf8 100644 --- a/core/tiny_alloc_fast.inc.h +++ b/core/tiny_alloc_fast.inc.h @@ -53,6 +53,8 @@ extern __thread uint32_t g_tls_sll_count[TINY_NUM_CLASSES]; extern int sll_refill_small_from_ss(int class_idx, int max_take); extern void* hak_tiny_alloc_slow(size_t size, int class_idx); extern int hak_tiny_size_to_class(size_t size); +extern int tiny_refill_failfast_level(void); +extern const size_t g_tiny_class_sizes[]; // Global Front refill config (parsed at init; defined in hakmem_tiny.c) extern int g_refill_count_global; @@ -182,10 +184,38 @@ static inline void* tiny_alloc_fast_pop(int class_idx) { if (__builtin_expect(g_tls_sll_enable, 1)) { void* head = g_tls_sll_head[class_idx]; if (__builtin_expect(head != NULL, 1)) { + // CORRUPTION DEBUG: Validate TLS SLL head before popping + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + size_t blk = g_tiny_class_sizes[class_idx]; + // Check alignment (must be multiple of block size) + if (((uintptr_t)head % blk) != 0) { + fprintf(stderr, "[TLS_SLL_CORRUPT] cls=%d head=%p misaligned (blk=%zu offset=%zu)\n", + class_idx, head, blk, (uintptr_t)head % blk); + fprintf(stderr, "[TLS_SLL_CORRUPT] TLS freelist head is corrupted!\n"); + abort(); + } + } + // Front Gate: SLL hit (fast path 3 instructions) extern unsigned long long g_front_sll_hit[]; g_front_sll_hit[class_idx]++; - g_tls_sll_head[class_idx] = *(void**)head; // Pop: next = *head + + // CORRUPTION DEBUG: Validate next pointer before updating head + void* next = *(void**)head; + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + size_t blk = g_tiny_class_sizes[class_idx]; + if (next != NULL && ((uintptr_t)next % blk) != 0) { + fprintf(stderr, "[ALLOC_POP_CORRUPT] Reading next from head=%p got corrupted next=%p!\n", + head, next); + fprintf(stderr, "[ALLOC_POP_CORRUPT] cls=%d blk=%zu next_offset=%zu (expected 0)\n", + class_idx, blk, (uintptr_t)next % blk); + fprintf(stderr, "[ALLOC_POP_CORRUPT] TLS SLL head block was corrupted (use-after-free/double-free)!\n"); + abort(); + } + fprintf(stderr, "[ALLOC_POP] cls=%d head=%p next=%p\n", class_idx, head, next); + } + + g_tls_sll_head[class_idx] = next; // Pop: next = *head // Optional: update count (for stats, can be disabled) if (g_tls_sll_count[class_idx] > 0) { diff --git a/core/tiny_refill_opt.h b/core/tiny_refill_opt.h index 44b5e6bf..38fd5e76 100644 --- a/core/tiny_refill_opt.h +++ b/core/tiny_refill_opt.h @@ -49,10 +49,31 @@ static inline void trc_push_front(TinyRefillChain* c, void* node) { } } +// Forward declaration of guard function +static inline int trc_refill_guard_enabled(void); + // Splice local chain into TLS SLL (single meta write) static inline void trc_splice_to_sll(int class_idx, TinyRefillChain* c, void** sll_head, uint32_t* sll_count) { if (!c || c->head == NULL) return; + + // CORRUPTION DEBUG: Validate chain before splicing + if (__builtin_expect(trc_refill_guard_enabled(), 0)) { + extern const size_t g_tiny_class_sizes[]; + size_t blk = g_tiny_class_sizes[class_idx]; + + fprintf(stderr, "[SPLICE_TO_SLL] cls=%d head=%p tail=%p count=%u\n", + class_idx, c->head, c->tail, c->count); + + // Check alignment of chain head + if (((uintptr_t)c->head % blk) != 0) { + fprintf(stderr, "[SPLICE_CORRUPT] Chain head %p misaligned (blk=%zu offset=%zu)!\n", + c->head, blk, (uintptr_t)c->head % blk); + fprintf(stderr, "[SPLICE_CORRUPT] Corruption detected BEFORE writing to TLS!\n"); + abort(); + } + } + if (c->tail) { *(void**)c->tail = *sll_head; } @@ -111,12 +132,26 @@ static inline uint32_t trc_pop_from_freelist(struct TinySlabMeta* meta, if (__builtin_expect(trc_refill_guard_enabled() && !trc_ptr_is_valid(ss_base, ss_limit, block_size, p), 0)) { + fprintf(stderr, "[FREELIST_CORRUPT] Reading freelist head: p=%p (ss_base=%p ss_limit=%p blk=%zu)\n", + p, (void*)ss_base, (void*)ss_limit, block_size); + fprintf(stderr, "[FREELIST_CORRUPT] Head pointer is corrupted (invalid range/alignment)\n"); trc_failfast_abort("freelist_head", class_idx, ss_base, ss_limit, p); } void* next = *(void**)p; if (__builtin_expect(trc_refill_guard_enabled() && !trc_ptr_is_valid(ss_base, ss_limit, block_size, next), 0)) { + fprintf(stderr, "[FREELIST_CORRUPT] Reading freelist node: p=%p next=%p (ss_base=%p ss_limit=%p blk=%zu)\n", + p, next, (void*)ss_base, (void*)ss_limit, block_size); + fprintf(stderr, "[FREELIST_CORRUPT] Next pointer is corrupted (cls=%d taken=%u/%u)\n", + class_idx, taken, want); + // Log offset details + if (next != NULL) { + uintptr_t offset = (uintptr_t)next - ss_base; + size_t expected_align = offset % block_size; + fprintf(stderr, "[FREELIST_CORRUPT] Corrupted offset=%zu (0x%zx) expected_align=%zu\n", + offset, offset, expected_align); + } trc_failfast_abort("freelist_next", class_idx, ss_base, ss_limit, next); } meta->freelist = next; @@ -134,14 +169,37 @@ static inline uint32_t trc_linear_carve(uint8_t* base, size_t bs, TinyRefillChain* out) { if (!out || batch == 0) return 0; trc_init(out); - uint8_t* cursor = base + ((size_t)meta->used * bs); + + // FIX: Use carved (monotonic) instead of used (decrements on free) + // CORRUPTION DEBUG: Validate capacity before carving + if (__builtin_expect(trc_refill_guard_enabled(), 0)) { + if (meta->carved + batch > meta->capacity) { + fprintf(stderr, "[LINEAR_CARVE_CORRUPT] Carving beyond capacity!\n"); + fprintf(stderr, "[LINEAR_CARVE_CORRUPT] carved=%u batch=%u capacity=%u (would be %u)\n", + meta->carved, batch, meta->capacity, meta->carved + batch); + fprintf(stderr, "[LINEAR_CARVE_CORRUPT] base=%p bs=%zu\n", (void*)base, bs); + abort(); + } + } + + // FIX: Use carved counter (monotonic) instead of used (which decrements on free) + uint8_t* cursor = base + ((size_t)meta->carved * bs); void* head = (void*)cursor; + + // CORRUPTION DEBUG: Log carve operation + if (__builtin_expect(trc_refill_guard_enabled(), 0)) { + fprintf(stderr, "[LINEAR_CARVE] base=%p carved=%u batch=%u cursor=%p\n", + (void*)base, meta->carved, batch, (void*)cursor); + } + for (uint32_t i = 1; i < batch; i++) { uint8_t* next = cursor + bs; *(void**)cursor = (void*)next; cursor = next; } void* tail = (void*)cursor; + // FIX: Update both carved (monotonic) and used (active count) + meta->carved += batch; meta->used += batch; out->head = head; out->tail = tail; diff --git a/core/tiny_remote.c b/core/tiny_remote.c index 23e5a9b8..ffb04f94 100644 --- a/core/tiny_remote.c +++ b/core/tiny_remote.c @@ -22,7 +22,7 @@ typedef struct { } rem_side_entry; static rem_side_entry g_rem_side[REM_SIDE_SIZE]; -int g_remote_side_enable = 0; +int g_remote_side_enable = 1; // 強制有効化: ブロックメモリへのnext埋め込みを回避 extern int g_debug_remote_guard; static _Atomic int g_remote_scribble_once = 0; static _Atomic uintptr_t g_remote_watch_ptr = 0; diff --git a/core/tiny_superslab_alloc.inc.h b/core/tiny_superslab_alloc.inc.h index 6549697e..8a95642b 100644 --- a/core/tiny_superslab_alloc.inc.h +++ b/core/tiny_superslab_alloc.inc.h @@ -82,6 +82,27 @@ static inline void* superslab_alloc_from_slab(SuperSlab* ss, int slab_idx) { // Freelist mode (after first free()) if (meta->freelist) { void* block = meta->freelist; + + // CORRUPTION DEBUG: Validate freelist head before popping + if (__builtin_expect(tiny_refill_failfast_level() >= 2, 0)) { + size_t blk = g_tiny_class_sizes[ss->size_class]; + uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx); + uintptr_t block_addr = (uintptr_t)block; + uintptr_t slab_addr = (uintptr_t)slab_base; + uintptr_t offset = block_addr - slab_addr; + + fprintf(stderr, "[ALLOC_POP] cls=%u slab=%d block=%p offset=%zu (used=%u cap=%u)\n", + ss->size_class, slab_idx, block, offset, meta->used, meta->capacity); + + if (offset % blk != 0) { + fprintf(stderr, "[ALLOC_CORRUPT] Freelist head is misaligned! block=%p offset=%zu blk=%zu\n", + block, offset, blk); + fprintf(stderr, "[ALLOC_CORRUPT] Expected alignment: %zu, actual: %zu\n", + blk, offset % blk); + tiny_failfast_abort_ptr("alloc_pop_misalign", ss, slab_idx, block, "freelist_head_corrupt"); + } + } + meta->freelist = *(void**)block; // Pop from freelist meta->used++; tiny_remote_track_on_alloc(ss, slab_idx, block, "freelist_alloc", 0); @@ -520,6 +541,14 @@ static inline void* hak_tiny_alloc_superslab(int class_idx) { int aligned = ((p - (uintptr_t)base) % block_size) == 0; int idx_ok = (tls->slab_idx >= 0) && (tls->slab_idx < ss_slabs_capacity(tls->ss)); if (!in_range || !aligned || !idx_ok || meta->used > (uint32_t)meta->capacity) { + // Diagnostic log before abort + fprintf(stderr, "[ALLOC_CARVE_BUG] cls=%u slab=%d used=%u cap=%u base=%p bs=%zu ptr=%p offset=%zu\n", + tls->ss->size_class, tls->slab_idx, meta->used, meta->capacity, + (void*)base, block_size, block, off); + fprintf(stderr, "[ALLOC_CARVE_BUG] in_range=%d aligned=%d idx_ok=%d used_check=%d\n", + in_range, aligned, idx_ok, meta->used > (uint32_t)meta->capacity); + fflush(stderr); + tiny_failfast_abort_ptr("alloc_ret_align", tls->ss, tls->slab_idx, diff --git a/core/tiny_superslab_free.inc.h b/core/tiny_superslab_free.inc.h index 7714dce3..9062fc36 100644 --- a/core/tiny_superslab_free.inc.h +++ b/core/tiny_superslab_free.inc.h @@ -278,8 +278,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { tiny_remote_watch_mark(ptr, "dup_prevent", my_tid); tiny_remote_watch_note("dup_prevent", ss, slab_idx, ptr, 0xA214u, my_tid, 0); tiny_debug_ring_record(TINY_RING_EVENT_REMOTE_INVALID, (uint16_t)ss->size_class, ptr, aux); - if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; } - return; + tiny_failfast_abort_ptr("double_free_remote", ss, slab_idx, ptr, "remote_side_contains"); } if (__builtin_expect(g_remote_side_enable && (head_word & 0xFFFFu) == 0x6261u, 0)) { // TLS guard scribble detected on the node's first word → same-pointer double free across routes @@ -288,8 +287,7 @@ static inline void hak_tiny_free_superslab(void* ptr, SuperSlab* ss) { tiny_remote_watch_mark(ptr, "pre_push", my_tid); tiny_remote_watch_note("pre_push", ss, slab_idx, ptr, 0xA231u, my_tid, 0); tiny_remote_report_corruption("pre_push", ptr, head_word); - if (g_tiny_safe_free_strict) { raise(SIGUSR2); return; } - return; + tiny_failfast_abort_ptr("double_free_scribble", ss, slab_idx, ptr, "scribble_6261"); } if (__builtin_expect(tiny_remote_watch_is(ptr), 0)) { tiny_remote_watch_note("free_remote", ss, slab_idx, ptr, 0xA232u, my_tid, 0);