Summary of completed work: 1. Header Corruption Bug - Root cause fixed in 2 freelist paths - box_carve_and_push_with_freelist() - tiny_drain_freelist_to_sll_once() - Result: 20-thread Larson 0 errors ✓ 2. Segmentation Fault Bug - Missing function declaration fixed - superslab_allocate() implicit int → pointer corruption - Fixed in 2 files with proper includes - Result: larson_hakmem stable ✓ Both bugs fully resolved via Task agent investigation + Claude Code ultrathink analysis. Updated files: - docs/status/CURRENT_TASK_FULL.md (detailed analysis) - docs/status/CURRENT_TASK.md (executive summary) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
84 KiB
Current Task (2025-11-29)
✅ COMPLETED: Header Corruption & Segfault Root Cause Fixes (2025-11-29)
背景
Claude Code (ultrathink) + Task agent による2つの critical bug の根治完了。
🐛 Bug 1: Class 1 Header Corruption
症状:
[TLS_SLL_HDR_RESET] cls=1 base=0x... got=0x00 expect=0xa1 count=0
ChatGPT Phase 1/2 対処:
- TLS SLL pop 時に header を書き直す対処療法を実装
- 結果: エラー頻度は減ったが根治せず
Root Cause Analysis (Task agent):
- freelist から block を pop → TLS SLL に push する2つのパスで header 未復元
- Freelist blocks には stale data (0x00) が残っている
- Header validation 時に corruption として検出
修正箇所:
-
core/box/carve_push_box.c:193-198-box_carve_and_push_with_freelist()- Freelist pop 後、TLS SLL push 前に header restoration 追加
- Commit:
3c6c76cb1
-
core/hakmem_tiny_free.inc:74-79-tiny_drain_freelist_to_sll_once()- Dead code path (HAKMEM_TINY_DRAIN_TO_SLL で activate)
- 同様に header restoration 追加
- Commit:
a94344c1a
Fix Pattern:
// CRITICAL FIX: Restore header BEFORE pushing to TLS SLL
#if HAKMEM_TINY_HEADER_CLASSIDX
*(uint8_t*)p = HEADER_MAGIC | (class_idx & HEADER_CLASS_MASK);
#endif
Results:
- ✅ 20-thread Larson test: Header corruption 完全消滅 (0 errors)
- ✅ 4-thread test: 残り1エラーも解消 (drain path 修正後)
- ✅ 全 freelist → TLS SLL paths で header restoration 完了
🐛 Bug 2: larson_hakmem Segmentation Fault
症状:
$ ./larson_hakmem 10 8 128 1024 1 12345 4
Segmentation fault (core dumped)
Root Cause Analysis (Task agent via gdb + coredump):
Location: core/superslab_head.c:87 in expand_superslab_head()
SuperSlab* new_chunk = superslab_allocate(head->class_idx);
The Problem:
superslab_allocate()の declaration が欠如- C99 標準により implicit
intreturn type を仮定 - 実際は
SuperSlab*(64-bit pointer) を返す - Compiler は 32-bit int として扱い、pointer corruption 発生
Corruption Mechanism:
1. superslab_allocate() returns 0x00005555eba00000 in %rax
2. Compiler expects int, reads only %eax: 0xeba00000
3. Assembly: movslq %eax,%rbp (sign-extend to 64-bit)
4. Bit 31 is set (0xeba00000 > 0x7fffffff) → sign extension
5. Result: 0xffffffffeba00000 (invalid pointer)
6. Dereference → SEGMENTATION FAULT
修正箇所 (Commit: 6d40dc741):
-
core/hakmem_tiny_superslab_internal.h:11#include "box/ss_allocation_box.h"追加- superslab_head.c が transitive に include
-
core/hakmem_super_registry.c:3#include "box/ss_allocation_box.h"追加- 同様の implicit declaration を修正
Warnings Eliminated:
✅ "implicit declaration of function 'superslab_allocate'"
✅ "type of 'superslab_allocate' does not match original declaration"
✅ "code may be misoptimized unless '-fno-strict-aliasing' is used"
Results:
- ✅ larson_hakmem が安定動作 (segfault 消滅)
- ✅ 2/4 threads 全テスト通過
- ✅ 複数 run で stability 確認
Impact:
- Severity: CRITICAL (affects all SuperSlab expansion)
- Frequency: Intermittent (~50% - depends on bit 31 of returned pointer)
- Scope: All multi-threaded workloads using SuperSlab
🎯 成功要因
-
対処療法 vs 根治の見極め
- ChatGPT の Phase 1/2 を「対処療法」と正しく評価
- Root cause (freelist header restoration) を追求
-
Task Agent の戦略的活用
- Header corruption: 全 freelist paths を網羅的探索
- Segfault: Assembly レベルの詳細解析
-
段階的アプローチ
- 調査 → 修正 → テスト → Commit
- 各段階で完全性を追求
-
詳細な記録
- 3つの commit message に root cause + mechanism を完全記録
- 将来の参考資料として価値あり
📝 Commits
a94344c1a Fix: Restore headers in tiny_drain_freelist_to_sll_once()
3c6c76cb1 Fix: Restore headers in box_carve_and_push_with_freelist()
6d40dc741 Fix: Add missing superslab_allocate() declaration
🔜 Next Steps
Both critical bugs are now fully resolved. The allocator is stable for:
- ✅ Multi-threaded Larson benchmarks
- ✅ Header validation enabled
- ✅ SuperSlab expansion under load
Recommend continuing with performance optimization work.
🔴 Phase 6-2.4: SuperSlab Guess Loop SEGV Fix (ARCHIVED - 2025-11-07)
問題
bench_random_mixed_hakmem と bench_mid_large_mt_hakmem が即座に SEGV
再現:
./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:
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:
- Registry lookup 失敗(alloc が SuperSlab 以外から)
- Guess loop で 1MB/2MB align した
guessを計算 - メモリマップ検証なし
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 で組み立てている疑いが濃厚。
- 例:
次のアクション
sll_refill_batch_from_ssに Fail-Fast を追加し、meta->freelist/*(void**)nodeが SuperSlab 範囲・アラインメント外だった場合に即座にログ&アボート(class, slab_idx, node, next, remote_heads も記録)。hak_tiny_free_superslab/tls_list_spill_excess/bg_spill_drain_classなどmeta->freelist = nodeを行う箇所で、prevが当該 SuperSlab 範囲かどうかをチェックするワンショットログを差し込み、どの経路で stale pointer が混入しているか切り分ける。- BG系ENVは2025-12 cleanupで廃止(常時OFF固定)。計測A/BはTLS_LISTやFAST_CAPなど現存ノブのみで実施する。
📊 ベンチマーク行列サマリ (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/<timestamp>/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 に接近)
- 特化分岐 ON:
-
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 への貼付を予定。
次ステップ
- 直リンク短ラン +
HAKMEM_SUPER_REG_REQTRACE=1+SIGUSR2(Tiny Debug Ring)の組合せで、hak_super_lookup前後の順序を突き止める。 /tmp/asan_*ログから[SUPER_REG] registerの時系列と Asan stack を抽出し、free→lookup→unregister の競合がないか記録。- 必要に応じて
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 クラッシュを発見:
再現手順:
./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
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 でクラッシュ再発
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 過剰蓄積またはメモリリーク?
次のアクション候補:
- TLS cache サイズ制限確認(
HAKMEM_TINY_FAST_CAP) - メモリリーク詳細調査(valgrind)
- デフォルト 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)
- 既定の「計測/検証」ルートを 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 …
- ASanが必要:
- 再起動ループ回避の運用ガード
- 長時間ASan 4T禁止、まず短時間/小負荷(例: ch/thread 256, sleep 5)で段階実行。
- CI/スクリプトで ASan 直リンク4Tをスキップし、LD_PRELOAD 方式へ統一。
- 根治(後追い・環境依存)
- オプション:
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):
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
// 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)
次のアクション:
-
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倍!)
-
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。
- Makefile に
- 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, 回帰なし)
- 退出ダンプ([EXIT DEBUG]/SS会計)を箱化
-
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の箱化 + 診断の最小追加)
-
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行級へ圧縮し、中央ハブとしての見通しを確保
- 新規:
-
Free 導通の最小スケスケ(短ラン限定・ワンショット)
- ENV:
HAKMEM_FREE_ROUTE_TRACE=1で最初の N 件だけ分類ログsuper(registry ok/miss)/mid(hit/miss)/l25(hit/miss)/unknown
- OOMを避けるため 2s/4T のみで実行。Tiny/SSへ届かない原因を最短で特定。
- ENV:
-
Phase 3 の見積もり(必要時)
- init/shutdown の箱化(
core/box/hak_core_init.inc.h) - 最終目標:
hakmem.cを中央 include ハブ(~400–600行)に固定
- init/shutdown の箱化(
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 のみ(他経路には侵食しない)。
- env
- Fail-Fast と戻せる設計:env で即時OFF可。
- BigCache-L25 ゲート(A/B)
- 実装:
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/<timestamp>/*.csv(ops/s, 備考列にENV)
- クイックCSV(5×4シナリオ×3アロケータ)を
bench_results_archive/に保存。
- ハーネス復元(
次アクション:
HAKMEM_BIGCACHE_L25=1で quick_full_benchmark を再実行し、vm/mixed の改善を確認。- 改善が見られれば、THPゲートとdecommitバッチのA/Bを追加実装(箱と境界は現行踏襲)。
🎯 次の主目標(mimalloc 対決: Larson/TinyHot)
- 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 のみ)。
- ベンチ・スナップショット
scripts/bench_capture_now.shで現構成を保存(日付入り)。scripts/profiles/tinyhot_best.envにREFILL_COUNT_HOT/FAST_CAPを追記しベストを固定。
- 比較と回帰防止
- Larson 2s/4s × threads=1/4/8 で hakmem vs mimalloc を測定・記録。
- Guard/ASan を OFF のまま長時間(>30s)を1回通して安定性を確認。
- 伸びなければ(次の一手)
- 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)
- SlabHandle Box(core/slab_handle.h, ~100行)
- 所有権取得・リモートキュードレイン・メタデータアクセスをカプセル化
- 型安全なハンドル(valid==1 のときのみ drain/modify が可能)
- Invariant: drain/push/pop は「所有権取得後のみ」実行可(境界1箇所)。
- 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 化
- 所有権なし 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 抑制で底上げ)
- 直近ターゲット(10秒計測)
作戦(Box Theory に基づく後段強化)
- Adopt/Ready 優先箱(取り出しO(1))
- Ready List(per-class slab hint)を最前段で採用。publish/remote/first-free で push、refill で pop→bind。
- Ready系ENVは2025-12 cleanupで廃止(常時ON固定, budget=1, width=TINY_READY_RING)。REG_SCAN_MAXのみ有効。
- 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 を可視に低減。
- L2.5(512KB–<2MB)も per‑site BigCache A/B(
可視化/計測(スクリプト整備済み)
- 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
- Larson triad:
- perf stat(PF/dTLB/IPC/branches)を10秒ランに併記(Larson/Mid中心)。
TODO(短期ロードマップ)
- Larson 2s→10s(1T/4T)で REG_SCAN_MAX × READY × ADOPT のA/B(CSV+perf)
- Mid/Large MT 10s(1T/4T)で採用窓とGate強化の最適点探索(CSV+perf)
- VM Mixed 狭帯域(512KB–1MB)で L25=ON/OFF の差を定量(CSV)
- Redis LD_PRELOAD 安定化(Tiny安全モード→段階的拡張)
- ベスト設定を
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 がゼロのままのため本質的な性能改善は未着手。
箱構成と境界
- FrontGate(fast-tier/Hot/TLS)の箱
tiny_fast_pop/push,hotmag_pop/push, TLS SLL/Magazine- ★ 現在の仮説:FAST_CAP=0 で front が完全にバイパスされる際、remote queue への戻し順序が乱れている
- RemoteQueue(ss_remote_push/dain)の箱
- SuperSlab remote_heads / remote_counts / slab_listed
tiny_publish_notifyが通知境界
- Mailbox/Publish の箱
tiny_mailbox_publish/fetch,ss_partial_publish/adopt
- Debug Ring(可視化の箱)
HAKMEM_TINY_TRACE_RING=1で alloc/free/publish 直前イベントをダンプ
現在の crash は FrontGate または RemoteQueue の内部バグが境界越えで露呈している状態 → 境界を固めて中/外どちらに異常があるか見極める。
進行中のタスク
- 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 の経路切り分け)tiny_remote_sentinel_set()で 0x6261 汚染を一度だけ捕捉し BT + SIGUSR2 を吐くトラップを追加(再現経路の特定用)- remote side table を箱化 (
1<<20+tiny_remote_side_clear()+side_overflowフォールバック) し、飽和時でも sentinel/next の整合を維持 - sentinel 汚染が発生した際の callstack を段階別(set/scan/drain)に収集し、同一ノードへの二重 push(実質 double free)発生箇所を pinpoint
- FrontGate バイパス検証
FAST_CAP=0時専用のトレースフラグHAKMEM_TINY_DEBUG_FAST0=1で front 層を最小順序に固定(Hot/TLS/Mag を飛ばして直接 remote → TLS リストへ流す)- フラグ ON/OFF で crash が消えるか確認 → front 内バグか remote 内バグかを切り分け
- 再現テスト・フラグ
scripts/run_larson_defaults.sh tputにHAKMEM_TINY_FAST_CAPオーバーライドを追加(忘れ防止)scripts/に crash 再現スクリプトrun_larson_fast0.shを用意(2s/4T で SIGSEGV を取得)
- publish pipeline 調査(二次優先)
- crash 解消後に
tiny_publish_notifyの発火率と mailbox drain を再計測し、mmap 偏重の根本原因へ戻る
- crash 解消後に
参考コマンド
- 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 <pid>で途中ダンプ、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 で進めるのが安全
当面の方針(追いつくまでの短期プラン)
-
Larson 用バイナリに ULTRA_SIMPLE を徹底適用してフロントのヒット率を最大化
- 目標: free/alloc ともに 3–4 命令の経路に乗せる(既に
free()は alignment-guess 経路有効) - ビルド:
EXTRA_CFLAGS="-DHAKMEM_TINY_PHASE6_ULTRA_SIMPLE=1 -DHAKMEM_BUILD_RELEASE=1"
- 目標: free/alloc ともに 3–4 命令の経路に乗せる(既に
-
Mixed/Larson の再測定と perf 取得
- コマンド:
scripts/run_larson.sh -d 2 -t 1,4 - 詳細:
scripts/run_larson_perf.sh(PF/IPC/branch/L1d を併記)
- コマンド:
-
迅速チューニング候補(小粒で効く順)
- 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) だが、統合経路でも分岐予測を安定化)
-
中期(必要なら)
- 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
- 変更:
- ULTRA_SIMPLE free を same-thread のみ直 push に制限。cross-thread free は従来経路へフォールバック。
-
3点セット(段階導入・既定OFF)
- remote queue(cross-thread free 時に per-slab MPSC stack へ)
- partial publish/adopt(クラス別公開リング→refill 前に adopt)
- 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行)
-
なぜこんなに速い?
- Ultra-simple fast path (分岐予測完璧)
- Perfect L1 cache locality (TLS array 64B)
- Freed blocks 即再利用 (LIFO で 100% hit)
- ゼロオーバーヘッド (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
-
根本原因分析:
-
Magazine capacity ミスチューン: 64 slots は ST workload には小さすぎる
- Batch=100 の場合、2回に1回は slow path に落ちる
- System allocator は tcache (7+ entries per size) で高速
-
Migration logic オーバーヘッド: Slow path での free list → Magazine migration が高コスト
- Batch migration (32 items) が頻繁に発生
- Pointer chase + atomic operations
-
Dual Free Lists の誤算: ST では効果ゼロ、むしろオーバーヘッド
- ST では remote_free は発生しない
- Dual structures のメモリ overhead のみ
-
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 必要)
-
次のアクション:
- ⏮️ Phase 5-B をロールバック (git revert)
- 📊 Baseline 再測定 (clean state で確認)
- 🤔 Phase 5-B-v2 を検討 (Magazine unification only, Dual Free Lists なし)
- 🎯 Alternative approach: Phase 6 系 (L25/SuperSlab 最適化) に移行
-
-
🚀 Phase 5-B-Simple 開始 (2025-11-03 04:00): Dual Free Lists + Magazine統合 🎯
-
Phase 5-A-v2 をスキップする理由:
- HAKMEM は既に O(1) size→class 変換 (
g_size_to_class_lut_1k[size]) - 多層 cache が既に 95%+ hit → direct cache 追加は効果薄い
- TLS 変数追加で cache pollution リスク
- 期待値: ±0% (効果とオーバーヘッドが相殺)
- HAKMEM は既に O(1) size→class 変換 (
-
Phase 5-B-Simple に集中 (期待: +15-23%)
-
2つの最適化を統合:
- Dual Free Lists (mimalloc Phase 5-B): +10-15%
- Local free で atomic 不要 (10+ cycles 削減)
- Cache locality 向上
- Magazine 統合 (シンプル化): +3-5%
- 3-4層 → 2層に削減
- TLS cache line を 3-4本 → 1-2本に削減
- Branch を 3-4回 → 1回に削減
- Dual Free Lists (mimalloc Phase 5-B): +10-15%
-
実装計画 (3-4 days):
// === 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 の核心:
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 変数:
// 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 の設計ミス
// 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つの要因:
- pthread_self() オーバーヘッド: 毎回
tiny_self_pt()syscall (~20-40 cycles) - Remote slab hit: Global cache が他 thread の slab を指す → Owner check → Cache clear → Fallback (無駄な3段階処理)
- 余分な分岐: 既存 fast path の前に新たな条件分岐層を追加
- pthread_self() オーバーヘッド: 毎回
- 測定結果 (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 共有
- mimalloc:
- Revert: 全ての Phase 5-A 変更を完全に revert (16.33 M ops/sec に復元)
- 次の戦略: Phase 5-A-v2 で thread-local slabs_direct を実装
// 次回の正しい実装方針 __thread TinySlab* g_tls_slabs_direct[129]; // ✅ Thread-local
- 実装: Global
-
🎊 Phase 5完了: 47% Gap の正体を解明! 🔍
- mimalloc 完全分析: 10,000語超の詳細レポート作成完了
- 3つのファイル生成:
MIMALLOC_ANALYSIS_REPORT.md- 詳細技術分析MIMALLOC_KEY_FINDINGS.md- 要約版MIMALLOC_IMPLEMENTATION_ROADMAP.md- 実装計画
- Gap の内訳判明:
- Direct Page Cache (O(1)): +15-20% ← 最大のボトルネック!
- Dual Free Lists: +10-15%
- 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で機能しない
// 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の教訓:
- ✅ TLS range check + 順序変更は理論通り動作(50% overhead削減)
- ❌ free() overheadが想定より小さかった(実測 ~8% vs 想定 ~21%)
- 💡 シングルスレッドでは mutex overhead は誤差レベル
- 🎯 さらなる改善には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% のオーバーヘッド が不要
修正方針:
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):
// 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変数アクセス + 分岐
修正方針:
// 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() 実装:
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% 削減
実装戦略:
// 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;
}
最適化ポイント:
- TLS range check を最初に実行 → Negative lookup を 1-2 cycles に!
- Registry lookup は TLS range内のみ実行 → Hit rate ~90%
- 既存のregistry_lookupを活用 → 安全性維持
実装要件
1. TLS heap range tracking:
// 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.cfree()のチェック順序を変更 (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.cfree(): 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
現在のフローチャート:
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):
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 |
| ❌ FAILED (-0.73%) | ||||
| Phase 2 (単体) | ~16.2 M ops/sec | 15.70 M ops/sec | 65% | ✅ 完了 (-3.3%, 予想通り) |
| Phase 2+1 Combined | 16.53 M ops/sec | 68% | ✅ 完了 (+1.8%) | |
| ❌ 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実装
# 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)
核心アプローチ:
- 3層への統合(現在の6-7層を削減)
- TLS-BUMP即値化(hot-class専用最適化)
- 段階的実装(即効 → 中期 → 長期)
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- デフォルト設定
コード例:
// 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%)
実装内容:
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%) |
| ❌ 失敗 (+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 (今日):
- ✅ ChatGPT Pro相談完了
- ✅ ドキュメント更新
- 🔧 Phase 4-A1実装(TLS-BUMP即値化)
- 🔧 ビルド & ベンチマーク
Day 2 (明日):
- 📊 Phase 4-A1結果分析
- 🔧 Phase 4-A2実装(小マガジン128)
- 📐 Phase 4-B詳細設計
- 🚀 Phase 4-B実装開始判断
設計原則
- 2レジスタ経路死守:
bcur/bendだけでalloc完結 - 層は最小3段:
TLS-BUMP → 小マガジン → Slow - ホット/コールド完全分離: データもコードも64B境界分離
- 統計はサンプルのみ: 1/16384、ホットパスに書き込みなし
- ヘッダ非更新: 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:
- Direct Page Cache (O(1)) が最大のボトルネック: +15-20%
- mimalloc も intrusive linked list を使用(Phase 3 の結論は正しい)
- Gap はマイクロ最適化から来る(データ構造選択ではない)
Phase 5-A: Direct Page Cache 実装
Goal: サイズ→ページ lookup を O(log n) から O(1) に
Current (HAKMEM):
// 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):
// Direct array index - O(1)
TinySlab* slab = g_tiny_pool.slabs_direct[size >> 3]; // 1 cycle!
Implementation Plan:
5-A-1. データ構造拡張
// 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 の更新
// 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 書き換え
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 風)
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 (進行中)
実装内容:
core/hakmem_tiny_simple.c新規作成- TLS Free List ベースの fast path (3-4 命令)
- SuperSlab からの refill (既存を流用)
ファイル:
core/hakmem_tiny_simple.c- シンプル版 Tiny allocatorcore/hakmem_tiny_simple.h- ヘッダ
ベンチマーク:
bench_tiny_hotで測定- 目標: System の 70-80%
成功の鍵
- Fast path を System tcache と同等に (3-4 命令)
- 学習層で差別化 (動的容量調整)
- Mid-Large の成功パターンを適用 (+171% の実績)