Files
hakmem/docs/analysis/TINY_CPU_HOTPATH_USERLAND_ANALYSIS.md
Moe Charm (CI) 0f15adae4e Phase ALLOC-GATE-OPT-1: tiny_alloc_gate_fast 統計計測
- AllocGateStats 構造体追加(size2class/route/env/class分布)
- malloc_tiny_fast にカウンタ埋め込み
- ENV: HAKMEM_ALLOC_GATE_STATS (default 0)
- 挙動変更なし(計測のみ)

計測結果:
- Mixed: total=542k, size2class=0, route_calls=0, env_checks=275k, C4-C7=95.2%
  - size_to_class/route_for_class は完全削減済み(LUT 効果)
  - C4-C7 が 95% → ULTRA fast path が有効
  - env_checks ≈ c7_calls → C7 ULTRA の ENV gate が毎回呼ばれる
- C6-heavy: total=11 → malloc_tiny_fast はほぼ通らない(mid/pool 主体)

結論:
- alloc gate は既に十分最適化済み(LUT + ULTRA で削減済み)
- さらなる最適化余地は小さい(env_checks は軽量化済み、数%以下の効果)
- 次フェーズでは free dispatcher (29%) や C7 ULTRA refill (7%) など、他のボトルネックを狙う

詳細: docs/analysis/ALLOC_GATE_ANALYSIS.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2025-12-11 21:32:40 +09:00

334 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# TINY CPU Hotpath Userland Analysis (Phase49)
- プロファイル: Mixed 161024B, ws=400, iters=1,000,000
`HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_TINY_LARSON_FIX=1`
- コマンド: `perf stat -e cycles:u,instructions:u,branch-misses:u ./bench_random_mixed_hakmem 1000000 400 1`
`perf record -g -e cycles:u -o perf.data.mixed_u ./bench_random_mixed_hakmem 1000000 400 1`
## perf statuserlandのみ
- Throughput: **41,000,086 ops/s**
- cycles:u=126,239,279 / instructions:u=324,810,642 → IPC≈2.57
- branch-misses:u=1,186,675
- time=0.0438suser 0.0295s / sys 0.0143s
## perf recordcycles:u上位シンボルself%
- free: 24.3% — front入口/libc wrapper 部分
- malloc: 18.0% — 同上
- main: 15.3% — ベンチハーネス
- tiny_heap_page_pop.lto_priv.0: 8.8% — TinyHeapBox ホット pop
- hak_super_lookup.lto_priv.*: 7.9% — Superslab 判定front→TinyHeapBox 境界前)
- tiny_heap_page_becomes_empty.constprop.0: 5.9% — empty 遷移処理
- __memset_avx2_unaligned_erms: 4.0% — ユーザランド初期化first-touch
- tiny_region_id_write_header: 2.4% — header 書き込み
- その他: __pthread_self / hak_free_at / tiny_heap_meta_flush_page などが1〜2%台
## 所感
- ベンチ自身の malloc/free/main が大きいが、allocator 側では **tiny_heap_page_pop / hak_super_lookup / tiny_heap_page_becomes_empty** が目立つ。
- userland側でも memset が残っており、first-touch 削減(ヘッダ書き込み削減や初期化遅延)余地がある。
## Phase50 で削るターゲット箱(提案)
- **TinyHeapBoxC7/C6の pop/empty + hak_super_lookup 前段**
- super_lookup 依存の範囲チェックを軽量化 or キャッシュ化。
- pop/empty 内の分岐を整理し、C7 SAFE の理想パスcurrent_page固定に寄せる。
- header write / memset を最小化する実験スイッチを検討。
## C7 v3 ON, Mixed 161024B (ws=400, iters=1,000,000, userland cycles)
環境: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_POOL_V2_ENABLED=0 HAKMEM_SMALL_HEAP_V3_ENABLED=1 HAKMEM_SMALL_HEAP_V3_CLASSES=0x80`
- Throughput: **48.96M ops/s**(前回 v3 ON と同レンジ)
- perf record `cycles:u` (171 samples, release build without symbols):
- 上位は `free` / `malloc` / `main` の無名フレームに潰れてしまい、非C7 Tiny front の細かい関数名が出ず。
- シンボル再取得には DEBUG/記号付きビルドで perf し直す必要あり。
- 所感: C7 v3 以外のホットパス特定には、size→class→route 前段や unified cache hit パスを再シンボル化して見る必要がある。現状のバイナリでは細部が見えない。
## Mixed 161024B (ws=400, iters=1,000,000, userland cycles, C7 v3 ON, DEBUGビルド)
環境: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_POOL_V2_ENABLED=0 HAKMEM_SMALL_HEAP_V3_ENABLED=1 HAKMEM_SMALL_HEAP_V3_CLASSES=0x80`
ビルド: `make clean && CFLAGS='-O2 -g …' USE_LTO=0 OPT_LEVEL=2 NATIVE=0 make bench_random_mixed_hakmem`
- Throughput: **46.146.3M ops/s**(リリース時の ~49M よりやや低いが符号付きビルドで許容)
- perf record `cycles:u -F5000 --call-graph dwarf` (200 samples) の self% 上位C7 v3 以外を抜粋):
- free 23.5%, malloc 18.8%, main 13.7%(ベンチハーネス由来)
- **tiny_region_id_write_header 6.7%**
- **ss_map_lookup 3.6%**
- unified_cache_enabled 2.8%
- tiny_guard_is_enabled 2.2%
- classify_ptr 1.4%size→class 判定系)
- mid_desc_lookup 1.3%
- hak_free_at 0.9%, hak_pool_mid_lookup 0.7%
- so_alloc/so_free 合計 ~7%C7 v3 本体)
- 所感非C7 Tiny front 目線):
- header 書き込みtiny_region_id_write_headerが依然目立つ。
- Superslab 判定前の `ss_map_lookup` が 34% 程度残っている。
- route/guard 判定unified_cache_enabled / tiny_guard_is_enabled / classify_ptrが合わせて ~6% 程度。
- 次は「size→class→route 前段header」をフラット化するターゲットが有力。
## TF3 事前計測DEBUGシンボル, front v3+LUT ON, C7-only v3
環境: `HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024 HAKMEM_TINY_HEAP_PROFILE=C7_SAFE HAKMEM_TINY_C7_HOT=1 HAKMEM_TINY_HOTHEAP_V2=0 HAKMEM_POOL_V2_ENABLED=0 HAKMEM_SMALL_HEAP_V3_ENABLED=1 HAKMEM_SMALL_HEAP_V3_CLASSES=0x80 HAKMEM_TINY_FRONT_V3_ENABLED=1 HAKMEM_TINY_FRONT_V3_LUT_ENABLED=1`
ビルド: `BUILD_FLAVOR=debug OPT_LEVEL=0 USE_LTO=0 EXTRA_CFLAGS=-g`
ベンチ: `perf record -F5000 --call-graph dwarf -e cycles:u -o perf.data.tiny_front_tf3 ./bench_random_mixed_hakmem 1000000 400 1`
Throughput: **12.39M ops/s**DEBUG/-O0 相当)
- `ss_map_lookup`: **7.3% self**free 側での ptr→SuperSlab 判定が主、C7 v3 でも多い)
- `hak_super_lookup`: **4.0% self**lookup fallback 分)
- `classify_ptr`: **0.64% self**free の入口 size→class 判定)
- `mid_desc_lookup`: **0.43% self**mid 経路の記述子検索)
- そのほか: free/malloc/main が約 30% 強、header write 系は今回のデバッグログに埋もれて確認できず。
所感:
## Phase HF1DEBUG, front v3+LUT+fast classify+mid_desc_cache ON
- ビルド: `CFLAGS='-O0 -g' USE_LTO=0 OPT_LEVEL=0 NATIVE=0`
- ENV: `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`, `HAKMEM_MID_DESC_CACHE_ENABLED=1`
- コマンド: `perf record -F 5000 --call-graph dwarf -e cycles:u -o perf.data.tiny_mixed_hf1 ./bench_random_mixed_hakmem 1000000 400 1`
- self% 上位perf_tiny_mixed_hf1.txt 抜粋):
- tiny_alloc_gate_fast 16.85%
- free 13.63% / malloc 13.34% / main 9.02%(ベンチ枠)
- __memset_avx2_unaligned_erms 5.65%(初期化)
- hak_super_registry_init 5.57%(初期化)
- so_alloc_fast 2.41%, unified_cache_push 2.23%
- tiny_front_v3_enabled 2.23%, tiny_front_v3_lut_lookup 2.21%
- smallobject_hotbox_v3_can_own_c7 1.94%
- tiny_region_id_write_header 1.82%
- ss_map_lookup 1.61%, mid_desc_lookup_cached 0.98%, classify_ptr 0.65%
所感: TF3 + mid_desc_cache 適用後、ss_map_lookup/self% は 1.6% まで沈み、tiny_region_id_write_header が引き続き ~1.8% で上位。次の削り候補は header 書き込み回数削減 or front前段の小枝刈り。
- front v3 + LUT ON でも free 側の `ss_map_lookup` / `hak_super_lookup` が ~11% 程度残っており、ここを FAST classify で直叩きする余地が大きい。
- `classify_ptr` は 1% 未満だが、`ss_map_lookup` とセットで落とせれば +5〜10% の目標に寄せられる見込み。
### Front v3 snapshot 導入メモ
- `TinyFrontV3Snapshot` を追加し、`unified_cache_on / tiny_guard_on / header_mode` を 1 回だけキャッシュする経路を front v3 ON 時に通すようにした(デフォルト OFF
- Mixed 161024B (ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF) で挙動変化なしslow=1 維持)。ホットスポットは依然 front 前段 (`tiny_region_id_write_header`, `ss_map_lookup`, guard/route 判定) が中心。
### Front v3 size→class LUT メモPhase2-A 実装済み、A/B これから)
- ENV `HAKMEM_TINY_FRONT_V3_LUT_ENABLED` を追加(デフォルト OFF。front v3 ON 時に size→class→route を Tiny 専用 LUT から 1 ルックアップで取得し、従来の `hak_tiny_size_to_class` + route 読みを代替する。
- LUT は起動時に既存の size→class 変換と route スナップショットをそのまま写経して構築するため挙動は変えない。
- A/B (Mixed 161024B, ws=400, iters=1M, C7 v3 ON, Tiny/Pool v2 OFF, front v3 ON):
- LUT=0: 44.820M ops/s
- LUT=1: 45.231M ops/s+0.9%
- HEAP_STATS は TinyHeap v1 経路外のため出力なし。C7_PAGE_STATS は prepare_calls=2446 で変化なし。
### Header v3 (C7-only) 簡易スキップ実験
- ENV: `HAKMEM_TINY_HEADER_V3_ENABLED` / `HAKMEM_TINY_HEADER_V3_SKIP_C7` を追加。C7 v3 alloc 時だけ tiny_region_id_write_header を通さず 1byte store にする。
- Mixed 161024B (ws=400, iters=1M, front v3 ON, LUT ON, route_fast=0, Tiny/Pool v2 OFF):
- header_v3=0: 44.29M ops/s, C7_PAGE_STATS prepare_calls=2446
- header_v3=1 + SKIP_C7=1: 43.68M ops/s約 -1.4%、prepare_calls=2446、fallback/page_of_fail=0
- 所感: C7 v3 のヘッダ簡略だけでは perf 改善は見えず。free 側のヘッダ依存を落とす or header light/off を別箱で検討する必要あり。
## TF3: ptr fast classify 実装後の A/BC7-only v3, front v3+LUT ON
- Releaseビルド, ws=400, iters=1M, ENV は TF3 基準 (`C7_SAFE`, C7_HOT=1, v2/pool v2=0, v3 classes=0x80, front v3/LUT ON)。
- Throughput (ops/s):
- PTR_FAST_CLASSIFY=0: **33.91M**
- PTR_FAST_CLASSIFY=1: **36.67M**(約 +8.1%
- DEBUG perf同ENV, gate=1, cycles@5k, dwarf: `ss_map_lookup` self が **7.3% → 0.9%**`hak_super_lookup` はトップから消失。代わりに TLS 内のページ判定 (`smallobject_hotbox_v3_can_own_c7` / `so_page_of`) が合計 ~5.5% へ移動。`classify_ptr` は 23% まで微増(外れ時のフォールバック分)。
- 所感: C7 v3 free の Superslab lookup 往復をほぼ除去でき、目標の +5〜10% に収まる結果。fast path 判定の TLS 走査が新たなホットスポットだが、現状コストは lookup より低く許容範囲。
## Phase PERF-ULTRA-REBASE-1 Results (2025-12-11 19:43:49)
### 計測環境
- **日時**: 2025-12-11 19:43:49
- **ワークロード**: Mixed 16-1024B, ws=8192, iters=10,000,000
- **ENV設定**:
- `HAKMEM_TINY_C7_ULTRA_FREE_ENABLED=1`
- `HAKMEM_TINY_C6_ULTRA_FREE_ENABLED=1`
- `HAKMEM_TINY_C5_ULTRA_FREE_ENABLED=1`
- `HAKMEM_TINY_C4_ULTRA_FREE_ENABLED=1`
- その他 ULTRA 以外のフラグは OFFv6/v4/v5/free-front-v3 等)
- **perf コマンド**: `perf record -F 5000 --call-graph dwarf -e cycles:u`
- **Throughput**: **31.61M ops/s**
- **Samples**: 1842 samples, 約1.36B cycles
### perf report 主要関数 self% トップ20
1. **free**: 25.48%libc wrapper/ベンチ由来)
2. **main**: 23.60%(ベンチマークハーネス)
3. **malloc**: 21.13%libc wrapper/ベンチ由来)
4. **tiny_c7_ultra_alloc**: 7.66%
5. **tiny_c7_ultra_free**: 3.50%
6. **so_free**: 2.47%
7. **so_alloc_fast**: 2.39%
8. **tiny_c7_ultra_page_of**: 1.78%
9. **classify_ptr**: 1.15%
10. **tiny_c7_ultra_segment_from_ptr**: 0.96%
11. **tiny_front_v3_lut_lookup**: 0.91%
12. **ss_map_lookup**: 0.79%
13. **tiny_c5_ultra_free_fast**: 0.69%
14. **hak_free_at**: 0.68%
15. **tiny_c6_ultra_free_fast**: 0.54%
16. **tiny_guard_is_enabled**: 0.45%
17. **tiny_c6_ultra_free_tls**: 0.34%
18. **tiny_heap_page_becomes_empty**: 0.23%
19. **tiny_c4_ultra_free_fast**: 0.17%
20. **tiny_c5_ultra_free_tls**: 0.17%
### カテゴリ別集計
- **ベンチマーク関連main + free + malloc wrapper**: 70.21%
- **C4-C7 ULTRA free関数群の合計**: 5.41%
- tiny_c7_ultra_free: 3.50%
- tiny_c5_ultra_free_fast: 0.69%
- tiny_c6_ultra_free_fast: 0.54%
- tiny_c6_ultra_free_tls: 0.34%
- tiny_c4_ultra_free_fast: 0.17%
- tiny_c5_ultra_free_tls: 0.17%
- **C7 ULTRA alloc**: 7.66%
- **so_alloc系v3 backend alloc**: 3.60%
- so_alloc_fast: 2.39%
- so_alloc: 1.21%
- **so_free系v3 backend free**: 2.47%
- **gate/front関連**: 2.51%
- classify_ptr: 1.15%
- tiny_front_v3_lut_lookup: 0.91%
- tiny_guard_is_enabled: 0.45%
- **page_of/segment判定**: 2.74%
- tiny_c7_ultra_page_of: 1.78%
- tiny_c7_ultra_segment_from_ptr: 0.96%
- **ss_map_lookupSuperslab判定**: 0.79%
- **hak_free_at**: 0.68%
- **tiny_heap_page_becomes_empty**: 0.23%
### 分析コメント
1. **C7 ULTRA alloc が最大ホットスポット7.66%**
- C7 ULTRA の allocate パスが allocator 内で最も重いボトルネック
- 次点は ULTRA free 群5.41%だが、alloc が約1.4倍重い
2. **so_alloc系v3 backendが3.60%で続く**
- C7 v3 の backend alloc 処理が依然として可視
- so_free は2.47%でバランス良好
3. **page_of/segment判定が2.74%**
- tiny_c7_ultra_page_of1.78%とsegment_from_ptr0.96%)の合計
- ULTRA free内でのptr→page/segment解決コストが目立つ
4. **gate/front前段は2.51%に留まる**
- classify_ptr1.15%、LUT lookup0.91%、guard判定0.45%
- 以前のフェーズより改善されており、現時点では相対的に軽い
5. **ss_map_lookup は0.79%まで低下**
- TF3 + PTR_FAST_CLASSIFY の効果で Superslab lookup が大幅減
- 依然残っているが、優先度は下がった
6. **header書き込みが不可視**
- tiny_region_id_write_header が上位20に入っていない< 0.17%
- ULTRA経路では header 書き込みコストが削減されている可能性
### 次の候補箱(ボトルネック優先順位)
1. **最優先: C7 ULTRA alloc7.66%**
- tiny_c7_ultra_alloc の内部最適化が最大の削減ポテンシャル
- キャッシュヒット率向上TLS構造簡素化分岐削減などを検討
2. **第2優先: C4-C7 ULTRA free群5.41%**
- 特に tiny_c7_ultra_free3.50%が中心
- page_of/segment判定2.74%との重複があるためptr解決の高速化が有効
3. **第3優先: so_alloc系 backend3.60%**
- C7 v3 backend alloc 処理の軽量化
- fast path のインライン化や TLS キャッシュ強化
4. **第4優先: page_of/segment判定2.74%**
- ptrpage/segment 解決の最適化
- TLS キャッシュや LUT ベースの高速化を検討
5. **監視対象: gate/front前段2.51%**
- 現状は許容範囲だがさらなる改善余地あり
- classify_ptr fast path 強化や LUT の効率化
### 所感
- **C7 ULTRA alloc が明確な最大ボトルネック**として浮上次のフェーズでは alloc パスの内部最適化TLS キャッシュヒット率向上構造簡素化分岐削減に注力すべき
- ULTRA free 群も5.41%と無視できないがalloc が約1.4倍重いためalloc を先に削るのが効率的
- gate/front 前段は以前のフェーズより改善されており現時点での優先度は下がった
- header 書き込みが上位20に入っていないのはULTRA 経路での削減効果が出ている可能性がある
## Phase PERF-ULTRA-REBASE-2: C4-C7 ULTRA free最適化後の hotpath分析 (2025-12-11)
### 計測条件
- **ENV**: `HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE`
- **ULTRA**: C4-C7 全て ON
- **v4/v5/v6/free-front-v3**: OFF
- **ワークロード**: Mixed 16-1024B (1M iter, ws=400)
- **perf コマンド**: `perf record -F 5000 --call-graph dwarf -e cycles:u`
- **Throughput**: **42.63M ops/s**前回 31.61M から大幅改善ws=400 vs 8192の差
### Mixed 16-1024B ホットパスself% 上位)
| 順位 | 関数 | self% | 分類 |
|------|------|-------|------|
| #1 | free | 27.88% | ベンチマーク wrapper |
| #2 | tiny_alloc_gate_fast | 23.57% | alloc gate/front |
| #3 | main | 17.66% | ベンチマーク harness |
| #4 | malloc | 6.94% | ベンチマーク wrapper |
| #5 | tiny_region_id_write_header.lto_priv.0 | 5.30% | header 書き込み |
| #6 | tiny_region_id_write_header.constprop.0 | 2.77% | header 書き込み |
| #7 | hak_super_lookup.lto_priv.1.lto_priv.0 | 0.69% | Superslab 判定 |
| #8 | hak_pool_free | 0.64% | pool free |
### 分析コメント
**WARNING: サンプル数が極めて少ない238 samples**
- perf 計測時のサンプル数が238と非常に少なく統計的信頼性が低い
- ベンチマーク時間が0.023s23msと極めて短くiters=1M / ws=400 の組み合わせでワークロードが軽すぎた可能性
- 結果としてallocator 本体のホットパスULTRA alloc/freeがほとんど可視化されていない
**可視化されたボトルネック**:
- **tiny_alloc_gate_fast**: 23.57% - alloc gate/front 入口が最大のホットスポット
- **header 書き込み合計**: 8.07%lto_priv.0: 5.30% + constprop.0: 2.77%
- **ULTRA 関連関数が不可視**: tiny_c7_ultra_alloc ULTRA free 群が上位20に入っていない
- **so_alloc/so_free も不可視**: v3 backend 処理も検出されず
**前フェーズPERF-ULTRA-REBASE-1との比較**:
- 前回ws=8192, iters=10M: C7 ULTRA alloc 7.66%, ULTRA free群 5.41%, so_alloc 3.60%
- 今回ws=400, iters=1M: これらが全て不可視< 0.63%
- **サンプル数**: 前回1842 samples vs 今回238 samples約1/8
**結論**:
- **計測条件の再検討が必要**: ワークロードを重くするiters 10M に増やすまたは ws を拡大
- 現状の結果では次の最大ボトルネックを確定できない
- gate/front23.57% header8.07%が可視範囲での上位だがこれは軽すぎるワークロードで allocator 本体が見えていない可能性が高い
### C6-heavy ホットパスself% 上位)
**計測条件**:
- **ENV**: `HAKMEM_PROFILE=C6_HEAVY_LEGACY_POOLV1`
- **Throughput**: **26.49M ops/s**
- **Samples**: 292 samplesやはり少ない
| 順位 | 関数 | self% | 分類 |
|------|------|-------|------|
| #1 | hak_pool_free_v1_impl.part.0 | 19.89% | pool v1 free |
| #2 | hak_pool_try_alloc_v1_impl.part.0 | 14.46% | pool v1 alloc |
| #3 | free | 11.80% | ベンチマーク wrapper |
| #4 | hak_free_at.constprop.0 | 9.26% | 解放処理 |
| #5 | pthread_once@GLIBC_2.2.5 | 8.21% | 初期化同期 |
| #6 | hak_super_lookup.lto_priv.1.lto_priv.0 | 7.07% | Superslab 判定 |
| #7 | malloc | 6.81% | ベンチマーク wrapper |
| #8 | __GI___pthread_self | 2.62% | TLS アクセス |
| #9 | hak_pool_free | 1.08% | pool free wrapper |
**分析**:
- **pool v1 が支配的**: alloc 14.46% + free 19.89% = 34.35%
- **ULTRA は可視化されず**: C6/C5/C4 ULTRA 関数が上位に入っていない
- **pthread_once が8.21%**: 初期化同期のオーバーヘッドが目立つワークロードが軽い証拠
**所感**: C6-heavy でも pool v1 が主要経路として機能しているがULTRA の効果測定には不十分なサンプル数
---
## Phase ALLOC-GATE-OPT-1 計測前の前提 (2025-12-11)
**最新 perfREBASE-3 より**:
- tiny_alloc_gate_fast: self% 18%
- tiny_route_for_class_calls: 267,967 calls alloc 側が主体
- FREE_DISPATCHER では ENV/route が既に snapshot で削減済み
alloc 側が未最適化の可能性が高い
**計測目的**:
- sizeclass 変換の回数毎回か
- route_for_class 呼び出し回数毎回か初期化時のみか
- alloc-side ENV check 回数C4-C7 ULTRA ENV gate
- クラス別分布C0C7 のどれが主体か
**期待される発見**:
- route_for_class alloc 毎に呼ばれているなら snapshot 化で削減可能
- size_to_class が重いなら インライン化LUT
- C4C7 80% 以上なら class-specific fast path 検討