Files
hakmem/PERF_ANALYSIS_16_1024B_20251205.md
2025-12-10 14:38:49 +09:00

29 KiB
Raw Permalink Blame History

16-1024B パフォーマンス分析レポート

分析日: 2025年12月5日 ベンチマーク: bench_random_mixed (1M iterations, ws=400, seed=1) サイズ範囲: 16-1024 bytes (Tiny allocator: 8 size classes)

Quick Baseline Refresh (2025-12-10, current HEAD, C7-only v3 / front v3 ON)

ENV (Release): HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE HAKMEM_BENCH_MIN_SIZE=16 HAKMEM_BENCH_MAX_SIZE=1024
bench_profile により TINY_HEAP_PROFILE=C7_SAFE TINY_C7_HOT=1 TINY_HOTHEAP_V2=0 SMALL_HEAP_V3_ENABLED=1 SMALL_HEAP_V3_CLASSES=0x80 POOL_V2_ENABLED=0 TINY_FRONT_V3_ENABLED=1 TINY_FRONT_V3_LUT_ENABLED=1 TINY_PTR_FAST_CLASSIFY_ENABLED=1 が自動注入される。FREE_POLICY/THP は未設定のまま)。

Allocator Throughput (ops/s) 備考
HAKMEM (current HEAD, C7-only v3, dedup OFF) 44,384,651 baseline (HAKMEM_PROFILE=MIXED_TINYV3_C7_SAFE)
HAKMEM + C7 header dedup ON 44,303,895 HAKMEM_TINY_C7_HEADER_DEDUP_ENABLED=1(ほぼ誤差)

Note: 一時的に同プロファイルで ~36M ops/s まで落ち込んでいたが、その後の Tiny front v3 snapshot の遅延・LUT 判定の整理・C7 ヘッダまわりの配置見直しにより、現 HEAD では同条件でおおよそ 44M ops/s レンジに戻っている。以降の最適化はこの数値を新しい Mixed 基準ラインとして扱う。

Quick Baseline Refresh (2025-12-05, C7-only v3 / front v3 ON)

ENV (Release): HAKMEM_BENCH_MIN_SIZE=16 MAX_SIZE=1024 TINY_HEAP_PROFILE=C7_SAFE TINY_C7_HOT=1 TINY_HOTHEAP_V2=0 SMALL_HEAP_V3_ENABLED=1 SMALL_HEAP_V3_CLASSES=0x80 POOL_V2_ENABLED=0front v3/LUT デフォルト ON, SMALL_HEAP_V3_STATS=1

Allocator Throughput (ops/s) Ratio vs mimalloc
HAKMEM (C7-only v3) 44,447,714 38.0%
mimalloc 117,204,756 100%
glibc malloc 90,952,144 77.6%

SmallObject v3 stats (cls7): route_hits=283,170 alloc_refill=2,446 alloc_fb_v1=0 free_fb_v1=0 page_of_fail=0。segv/assert なし。


エグゼクティブサマリー

HAKMEMの16-1024Bサイズ範囲でのパフォーマンスは、競合アロケータと比較して著しく低い。主要原因は、SuperSlabアロケーション時の過剰なページフォルト約46-50倍多いであり、これによりカーネル時間が全体の約86%237ms / 276msを占めている。

測定結果サマリー

アロケータ スループット (ops/s) 相対性能 ページフォルト サイクル数
mimalloc 120,698,029 100% 146 35,872,448
libc malloc 83,321,226 69.0% 134 48,695,085
HAKMEM 4,844,380 4.0% 6,762 1,138,338,103

性能差の規模

  • HAKMEMはmimallocの約1/25の性能25倍遅い
  • HAKMEMはlibc mallocの約1/17の性能17倍遅い
  • ページフォルト数は約46-50倍多いmimalloc: 146, HAKMEM: 6,762
  • CPU サイクル数は約32倍多いmimalloc: 35.8M, HAKMEM: 1.14B

Phase 1: ベンチマーク測定(詳細)

測定条件

# テスト構成
iterations: 1,000,000
working set: 400 slots
seed: 1
サイズ分布: 16 + (rand() & 0x3FF) = 161040 bytes (均等分布)

HAKMEM測定結果3回実行

Run 1: 4,838,027 ops/s (time=0.207s)
Run 2: 4,893,096 ops/s (time=0.204s)
Run 3: 4,802,016 ops/s (time=0.208s)
平均: 4,844,380 ops/s
標準偏差: 37,851 ops/s (0.78%)

mimalloc測定結果3回実行

Run 1: 124,653,448 ops/s (time=0.008s)
Run 2: 118,312,154 ops/s (time=0.008s)
Run 3: 119,128,485 ops/s (time=0.008s)
平均: 120,698,029 ops/s
標準偏差: 2,810,883 ops/s (2.33%)

libc malloc測定結果3回実行

Run 1: 89,740,104 ops/s (time=0.011s)
Run 2: 82,601,696 ops/s (time=0.012s)
Run 3: 77,621,877 ops/s (time=0.013s)
平均: 83,321,226 ops/s
標準偏差: 4,947,858 ops/s (5.94%)

Phase 2: 性能比較分析

相対性能mimallocを100%として)

アロケータ スループット比 性能ギャップ
mimalloc 100.0% ベースライン
libc malloc 69.0% -31.0%
HAKMEM 4.0% -96.0%

実行時間の内訳perf stat

HAKMEM (16-1024B)

Total time: 0.276s
  User time: 0.038s (13.8%)
  Sys time:  0.238s (86.2%)  ← カーネル時間が支配的
Page faults: 6,762
Cycles: 1,138,338,103

mimalloc (16-1024B)

Total time: 0.011s
  User time: 0.010s (90.9%)
  Sys time:  0.001s (9.1%)
Page faults: 146
Cycles: 35,872,448

libc malloc (16-1024B)

Total time: 0.013s
  User time: 0.012s (92.3%)
  Sys time:  0.001s (7.7%)
Page faults: 134
Cycles: 48,695,085

重要な発見: HAKMEMの実行時間の86%がカーネル時間システムコールであり、これはページフォルトとmadvise()呼び出しによるものです。


Phase 3: perf profiling分析

CPU時間トップ10cycles event

58.62%  カーネル: madvise() syscall
  ├─ 53.19%  __x64_sys_madvise
  │   ├─ 44.53%  follow_page_mask (ページテーブルウォーク)
  │   └─ 8.66%   handle_mm_fault (ページフォルト処理)
  │       ├─ 7.60%  do_anonymous_page (新規ページアロケーション)
  │       │   ├─ 5.23%  alloc_anon_folio
  │       │   │   └─ 4.18%  __alloc_pages
  │       │   │       ├─ 2.07%  clear_page_erms (ページゼロクリア)
  │       │   │       ├─ 1.06%  cond_accept_memory
  │       │   │       └─ 1.04%  rmqueue
  │       │   ├─ 1.06%  folio_add_lru_vma
  │       │   └─ 1.02%  folio_add_new_anon_rmap
  │       └─ 1.05%  pte_offset_map_nolock
  ├─ 4.00%  __x64_sys_exit_group (終了処理)
  │   └─ 2.06%  tlb_flush_mmu
  └─ 1.01%  __x64_sys_munmap

ページフォルト統計perf stat

メトリック HAKMEM mimalloc libc HAKMEM/mimalloc比
Page faults 6,762 146 134 46.3倍
Minor faults 6,762 146 134 46.3倍
Major faults 0 0 0 -

重要: すべてのページフォルトはminor faults既にメモリ内にあるがページテーブルエントリが未設定であり、ディスクI/Oは発生していない。しかし、カーネルのページフォルト処理オーバーヘッドが莫大。


Phase 4: サイズクラス分析とコードパス調査

HAKMEMのサイズクラス構成Tiny allocator

16-1024Bサイズ範囲は、以下の8つのTinyサイズクラスでカバーされる

クラス サイズ SuperSlab内オブジェクト数 SuperSlabサイズ
C1 16B 65,536 1MB
C2 32B 32,768 1MB
C3 64B 16,384 1MB
C4 128B 8,192 1MB
C5 256B 4,096 1MB
C6 512B 2,048 1MB
C7 1KB 1,024 1MB
(C0) (8B) (131,072) (1MB)

ベンチマークログからの観測:

[WARMUP] SuperSlab prefault: 100000 warmup iterations (not timed)...
[SP_INTERNAL_ALLOC] class_idx=2   ← 32Bクラス
[SS_ALLOC_PATH] pf_policy=0 populate=0 size_class=2
[SS_REG_DEBUG] class=2 ss=0x727500b00000 reg_ok=1 map_count=1
[SP_INTERNAL_ALLOC] class_idx=6   ← 512Bクラス
[SS_ALLOC_PATH] pf_policy=0 populate=0 size_class=6
[SS_REG_DEBUG] class=6 ss=0x727500a00000 reg_ok=1 map_count=2
[SP_INTERNAL_ALLOC] class_idx=7   ← 1KBクラス
[SS_ALLOC_PATH] pf_policy=0 populate=0 size_class=7
[SS_REG_DEBUG] class=7 ss=0x727500900000 reg_ok=1 map_count=3
[SP_INTERNAL_ALLOC] class_idx=7   ← 1KBクラス2つ目
[SS_REG_DEBUG] class=7 ss=0x727500800000 reg_ok=1 map_count=4
[WARMUP] Complete. Allocated=50098 Freed=49902 SuperSlabs populated.

割り当てパス(アロケーションフロー)

malloc(size)
  → tiny_alloc(size)
    → class_idx = size_to_class(size)    // 8クラスのいずれか
    → tiny_fast_pop(class_idx)            // Fast cache (per-class front cache)
      → HIT: return block
      → MISS: tiny_tls_mag_refill()       // TLS magazine refill
        → sll_pop() or shared_pool_acquire()
          → SuperSlab refill required
            → superslab_alloc()           // ← ここでページフォルト発生
              → mmap(1MB, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS)
                → カーネル: 256ページフォルト (1MB / 4KB = 256)

ページフォルトの発生源

1. SuperSlabアロケーション時主要

各サイズクラスで初めてSuperSlabをアロケートする際に発生

  • 1SuperSlabあたり256ページフォルト1MB / 4KB = 256ページ
  • ベンチマークで観測された4つのSuperSlab: 4 × 256 = 1,024ページフォルト

2. Partial Release機能副次的

superslab_partial_release() 関数(hakmem_tiny_intel.inc:685-698:

static inline void superslab_partial_release(SuperSlab* ss, uint32_t epoch) {
#if defined(MADV_DONTNEED)
    if (!g_ss_partial_enable) return;
    if (!ss || ss->magic != SUPERSLAB_MAGIC) return;
    uint32_t prev = ss->partial_epoch;
    if (epoch != 0 && (epoch - prev) < g_ss_partial_interval) return;
    size_t len = (size_t)1 << ss->lg_size;
    if (madvise(ss, len, MADV_DONTNEED) == 0) {  // ← SuperSlab全体(1MB)をmadvise
        ss->partial_epoch = epoch;
    }
#endif
}

問題点:

  • SuperSlab全体1MB = 256ページに対してmadvise(MADV_DONTNEED)を呼び出す
  • これにより、次回アクセス時に再度256ページフォルトが発生
  • refill間隔g_ss_partial_interval)のたびに実行される可能性がある

3. Warmup中のページフォルト内訳

Total page faults: 6,762
  - Initial SuperSlab allocation: ~1,024 (4 SuperSlabs × 256)
  - Additional faults: ~5,738
    - Partial release後の再アクセス
    - 他のTinyクラスやPool/SmallMidアロケーション
    - カーネルのメタデータページ

Phase 4.5: Partial Release無効化A/Bテスト

仮説

Partial Release機能が過剰なmadvise()呼び出しとページフォルトを引き起こしているのでは?

実験設定

# Partial Release無効化
HAKMEM_TINY_SS_PARTIAL=0 ./bench_random_mixed_hakmem 1000000 400 1

結果

設定 スループット (ops/s) ページフォルト 改善率
Partial Release有効 (デフォルト) 4,844,380 6,762 ベースライン
Partial Release無効 4,942,831 6,778 +2.0%

結論

Partial Release機能は主要な性能ボトルネックではない

  • スループット改善はわずか2.0%(誤差範囲内)
  • ページフォルト数もほぼ同じ6,762 vs 6,778
  • 主要なページフォルトは初期SuperSlabアロケーション時に発生している

Phase 5: 根本原因分析

性能差の根本原因(重要度順)

1. SuperSlabアロケーション戦略の非効率性(最重要)

問題:

  • HAKMEMは各サイズクラスに対して1MBのSuperSlabをmmap()でアロケート
  • mmap()直後、ページはカーネルにマッピングされているが物理メモリは未割り当て
  • 初回アクセス時にページフォルトが発生し、カーネルがページを割り当て
  • 1MB SuperSlab = 256ページ × 4KB = 256ページフォルト

mimalloc/libcとの違い:

  • mimallocはより小さいチャンク4KB-64KBを使用し、段階的に拡張
  • libcはsbrk()やより効率的なmmap()戦略を使用
  • 両者とも初期アロケーションのページフォルトが少ない

証拠:

HAKMEM:      6,762 page faults / 1M operations = 6.76 faults/1000 ops
mimalloc:    146 page faults / 1M operations = 0.15 faults/1000 ops
libc:        134 page faults / 1M operations = 0.13 faults/1000 ops

2. カーネル時間の支配(システムコールオーバーヘッド)

perf stat分析:

HAKMEM実行時間: 0.276s
  User time: 0.038s (13.8%)
  Sys time:  0.238s (86.2%)  ← カーネル時間が支配的

内訳:

  • 58.62%の時間がmadvise()システムコール内で消費
  • ページフォルト処理にかかる時間が莫大
  • ページテーブルウォーク、ページアロケーション、TLBフラッシュなど

対比:

mimalloc: User 90.9%, Sys 9.1%   ← ユーザー空間で完結
libc:     User 92.3%, Sys 7.7%   ← ユーザー空間で完結
HAKMEM:   User 13.8%, Sys 86.2%  ← カーネルで大半を消費

3. ページフォルトあたりのコスト

1ページフォルトあたりのコスト推定:

HAKMEM総CPU時間: 276ms
ページフォルト数: 6,762
1ページフォルトあたり: 276ms / 6,762 = 40.8μs

対比:
mimalloc: 11ms / 146 = 75.3μs per fault
libc:     13ms / 134 = 97.0μs per fault

HAKMEMのページフォルトコストがやや低いのは、カーネルが連続した領域のフォルトを効率的に処理しているため。しかし、フォルト数自体が46倍多いため、総コストは莫大。

4. ワーキングセット効率の低さ

HAKMEMの問題:

  • 小規模ベンチマークws=400, 1M iterationsでも4つのSuperSlabをアロケート
  • 各SuperSlabは1MB = 256ページ = 合計**1,024ページ4MB**を消費
  • しかし、実際に使用されるメモリはごくわずか400オブジェクト × 平均サイズ ~500B = ~200KB

メモリ効率:

割り当てメモリ: 4MB (4 SuperSlabs)
実使用メモリ:   ~200KB
効率:          200KB / 4MB = 5%  ← 95%が無駄

mimalloc/libcの戦略:

  • 必要な分だけ段階的にアロケートdemand paging
  • より小さい粒度でメモリを管理
  • ページフォルトが発生する前にprefetchやMAP_POPULATEなどの最適化を活用

5. madvise()呼び出しの頻度Partial Release

Partial Release機能がオンの場合、定期的にSuperSlab全体にmadvise()を実行:

// hakmem_tiny_intel.inc:685-698
if (madvise(ss, 1MB, MADV_DONTNEED) == 0) { ... }

影響:

  • madvise()はシステムコールであり、カーネルモードへの遷移コストがかかる
  • MADV_DONTNEEDは物理ページを解放し、次回アクセス時に再度ページフォルト
  • しかし、A/Bテストの結果、この影響はわずか2%(副次的)

比較: 8-128B vs 16-1024B

8-128Bサイズ範囲での測定

./bench_tiny_hot_hakmem 1000000 400 1

結果:

Throughput: ~88M ops/s  (16-1024Bの約18倍速い)
Page faults: 6,734      (16-1024Bとほぼ同じ)
Cycles: 742,201,607     (16-1024Bの約65%)

比較分析

メトリック 8-128B 16-1024B 比率
スループット ~88M ops/s 4.84M ops/s 18.2倍
ページフォルト 6,734 6,762 1.00倍
CPUサイクル 742M 1,138M 0.65倍

重要な発見:

  1. ページフォルト数はほぼ同じ(サイズ範囲に依存しない)

    • これは、ページフォルトがSuperSlabアロケーション時に集中していることを示す
  2. 8-128Bが18倍速い理由:

    • サイズクラスが少ないC0-C4の5クラス vs C0-C7の8クラス
    • オブジェクトサイズが小さく、キャッシュ効率が高い
    • より少ないSuperSlabで済むメモリ効率が良い
  3. 16-1024Bが遅い理由:

    • 大きなオブジェクト512B, 1KBのキャッシュ効率が低い
    • より多くのSuperSlabが必要クラスが多い
    • 同じページフォルト数でも、処理するデータ量が多い

推奨される次の最適化

優先度1: SuperSlabプリフォールト最適化 【即効性: 高】

現状の問題:

  • mmap()後、ページは仮想アドレス空間にマッピングされるが物理メモリは未割り当て
  • 初回アクセス時にページフォルトが発生
  • 1MB SuperSlab = 256ページフォルト

提案:

1-A. MAP_POPULATE使用最優先

// hakmem_tiny_superslab.c: superslab_alloc()
void* ptr = mmap(NULL, SUPERSLAB_SIZE,
                 PROT_READ | PROT_WRITE,
                 MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE,  // ← 追加
                 -1, 0);

効果:

  • mmap()時にカーネルがページを事前割り当て
  • ページフォルトをバッチ処理TLBフラッシュが1回で済む
  • 期待効果: 5-10倍の性能向上カーネル時間を86% → 10%以下に削減)

トレードオフ:

  • 初期mmap()が遅くなるただし、warmup中なので問題ない
  • RSS物理メモリ使用量が増えるただし、現在も4MBは使っているので変わらない

1-B. madvise(MADV_WILLNEED)による明示的プリフェッチ

// SuperSlabアロケーション直後
void* ss = mmap(...);
madvise(ss, SUPERSLAB_SIZE, MADV_WILLNEED);  // カーネルにページ事前準備を通知

効果:

  • カーネルがバックグラウンドでページを準備
  • ページフォルトを事前に解決
  • 期待効果: 3-5倍の性能向上

1-C. 手動メモリタッチfallback

// SuperSlabアロケーション直後
void* ss = mmap(...);
// 各ページの最初のバイトに書き込んでページフォルトを強制
for (size_t i = 0; i < SUPERSLAB_SIZE; i += 4096) {
    ((volatile char*)ss)[i] = 0;
}

効果:

  • 確実にページフォルトを強制
  • MAP_POPULATEが使えない環境でも動作
  • 期待効果: 2-4倍の性能向上

推奨実装順序:

  1. MAP_POPULATEを試す最も効果が高い
  2. 効果が不十分ならMADV_WILLNEEDを追加
  3. 古いカーネルではfallbackとして手動タッチ

優先度2: SuperSlabサイズのチューニング 【即効性: 中】

現状の問題:

  • 1MB SuperSlabは大きすぎる小規模ワークロードで無駄が多い
  • 小規模ベンチマークws=400で4MB使うのは非効率実使用は200KB

提案:

2-A. サイズクラス別SuperSlabサイズ

// 大きなオブジェクトほど小さいSuperSlabを使う
size_t superslab_size_for_class(int class_idx) {
    if (class_idx <= 2) return 1024 * 1024;  // 16-32B: 1MB
    if (class_idx <= 4) return 512 * 1024;   // 64-128B: 512KB
    return 256 * 1024;                       // 256B-1KB: 256KB
}

効果:

  • 大きなオブジェクトクラスのページフォルトを削減1KB: 256→64ページフォルト
  • RSS削減4MB → ~1.5MB
  • 期待効果: 1.5-2倍の性能向上

2-B. アダプティブSuperSlabサイズ

// ワークロードに応じてSuperSlabサイズを動的調整
// 小規模ワークロード: 256KB
// 大規模ワークロード: 1MB-2MB

効果:

  • ワークロードに最適化
  • 期待効果: 1.3-1.8倍の性能向上

優先度3: Partial Release戦略の見直し 【即効性: 低】

現状の問題:

  • SuperSlab全体1MBにmadvise()を実行
  • 次回アクセス時に再度256ページフォルト

提案:

3-A. Partial Releaseのデフォルト無効化

// hakmem_tiny_config.h
#define TINY_SS_PARTIAL_ENABLE_DEFAULT 0  // 現在: 1

効果:

  • A/Bテストで2%の改善を確認済み
  • 期待効果: 1.02倍の性能向上(すでに測定済み)

3-B. 粒度の細かいPartial Release

// SuperSlab全体ではなく、未使用Slabのみをリリース
for (int slab_idx = 0; slab_idx < SLABS_PER_SUPERSLAB; slab_idx++) {
    if (slab_is_empty(ss, slab_idx)) {
        madvise(ss->slabs[slab_idx], SLAB_SIZE, MADV_DONTNEED);
    }
}

効果:

  • より細かい粒度でメモリ解放
  • ページフォルトを最小化
  • 期待効果: 1.1-1.3倍の性能向上

優先度4: アロケーションパスの最適化 【即効性: 中】

提案:

4-A. Fast Cache容量の増加大きなオブジェクト

// hakmem_tiny_config.h
// 512B-1KBクラスのFast Cache容量を増やす
g_fast_cap_defaults[6] = 128;  // 512B: 64 → 128
g_fast_cap_defaults[7] = 128;  // 1KB:  64 → 128

効果:

  • SuperSlabへのアクセス頻度削減
  • ページフォルトトリガーの減少
  • 期待効果: 1.2-1.5倍の性能向上

4-B. TLS Magazine容量の動的調整

// ACE学習を活用して、16-1024Bワークロードに最適化
// 現在のACEは8-128Bに最適化されている可能性

効果:

  • より効率的なキャッシング戦略
  • 期待効果: 1.1-1.3倍の性能向上

総合最適化の期待効果

段階的最適化シナリオ

フェーズ 最適化内容 期待スループット 累積改善
現状 ベースライン 4.8M ops/s 1.0x
Phase 1 MAP_POPULATE追加 24-48M ops/s 5-10x
Phase 2 SuperSlabサイズ調整 36-86M ops/s 7.5-18x
Phase 3 Partial Release無効化 37-88M ops/s 7.7-18.2x
Phase 4 Cache容量最適化 44-110M ops/s 9.2-23x
目標 mimalloc並み 120M ops/s 25x

現実的な改善予測

保守的見積もりPhase 1のみ:

  • MAP_POPULATE実装: 5倍の性能向上 → 24M ops/s
  • これだけでlibcの29%、mimallocの20%まで到達

楽観的見積もりPhase 1-4全実装:

  • 累積効果: 15-20倍の性能向上 → 72-96M ops/s
  • libcを上回り、mimallocの60-80%まで到達

mimalloc並みを達成するには:

  • 上記最適化に加えて、アロケーションパス全体の根本的な見直しが必要
  • より小さい粒度のメモリ管理4KB-64KBチャンク
  • madvise()呼び出しの完全排除(またはバックグラウンド化)

技術メモ

SuperSlabアーキテクチャの設計トレードオフ

現在の設計1MB SuperSlabの利点:

  1. アドレス空間の効率的管理: 1MBアラインメントで高速なポインタ→SuperSlab逆引き
  2. メタデータの局所性: SuperSlabヘッダーと実データが同じ1MB領域に格納
  3. キャッシュフレンドリー: 大きなチャンクでTLBエントリを節約

現在の設計の欠点(本分析で判明):

  1. 初期ページフォルトコストが高い: 1MB = 256ページフォルト
  2. 小規模ワークロードで非効率: ws=400でも4MB使う実使用200KB
  3. Partial Releaseの粒度が粗い: 1MB全体をリリースすると次回256ページフォルト

mmap()とページフォルトのメカニズム

Linuxカーネルの動作:

1. mmap()呼び出し
   ↓
2. カーネルが仮想アドレス空間を予約VMA作成
   ← この時点では物理メモリは未割り当て
   ↓
3. ユーザー空間でメモリアクセス
   ↓
4. ページフォルト発生CPUがMMUを通じてカーネルにトラップ
   ↓
5. カーネルがdo_anonymous_page()を呼び出し
   ├─ 物理ページをアロケート__alloc_pages
   ├─ ページをゼロクリアclear_page_erms
   ├─ ページテーブルエントリを設定
   └─ TLBにエントリを追加
   ↓
6. ユーザー空間に戻る

ページフォルトのコスト:

  • CPU: ~1000-3000サイクル約0.3-1.0μs @ 3GHz
  • カーネル処理: ~40μs本分析の測定値
  • 合計: 約40μs/ページフォルト

1MB SuperSlab = 256ページフォルト = 10.2msのオーバーヘッド

MAP_POPULATEの効果

MAP_POPULATEの動作:

void* ptr = mmap(NULL, size, PROT_READ|PROT_WRITE,
                 MAP_PRIVATE|MAP_ANONYMOUS|MAP_POPULATE, -1, 0);

カーネルの処理:

  1. VMA作成通常のmmapと同じ
  2. faultin_page_range()を呼び出し、全ページを事前割り当て
  3. ページフォルトをバッチ処理TLBフラッシュが1回で済む
  4. ユーザー空間に戻る

利点:

  • ページフォルトのオーバーヘッドを5-10倍削減
  • TLBフラッシュを1回に集約
  • カーネル/ユーザー空間の遷移を削減

欠点:

  • mmap()の実行時間が長くなるただし、warmup中なので問題ない
  • RSS物理メモリ使用量が即座に増加

競合アロケータの戦略

mimalloc

  • 小さいチャンク: 4KB-64KB1MBではない
  • 段階的拡張: 必要に応じて徐々に拡張
  • ページフォルトの分散: 一度に大量のページフォルトが発生しない
  • TLS最適化: スレッドローカルキャッシュが非常に効率的

libc malloc (glibc)

  • arenasベース: スレッドごとにarenaを持つ
  • small bins/large bins: サイズ別に効率的な管理
  • sbrk()とmmap()の併用: 小さい割り当てにはsbrk()、大きい割り当てにはmmap()
  • チューニング済み: 数十年の最適化の蓄積

HAKMEM改善前

  • 大きいチャンク: 1MB SuperSlab最初から大きい
  • 一括アロケーション: 初期に大量のページフォルト
  • Partial Release: 積極的なメモリ解放(ただし再フォルトコスト)
  • 最適化余地: 上記提案により5-25倍の改善が可能

次のステップ

  1. Phase 1実装 (優先度: 最高)

    # MAP_POPULATEを追加
    git checkout -b opt/map-populate
    # hakmem_tiny_superslab.c を編集
    make bench_random_mixed_hakmem
    ./bench_random_mixed_hakmem 1000000 400 1
    # 期待結果: 24-48M ops/s5-10倍改善
    
  2. Phase 2実装 (優先度: 高)

    # サイズクラス別SuperSlabサイズ
    git checkout -b opt/adaptive-superslab-size
    # hakmem_tiny_config.h を編集
    make bench_random_mixed_hakmem
    ./bench_random_mixed_hakmem 1000000 400 1
    # 期待結果: 36-72M ops/s累積7.5-15倍改善
    
  3. Phase 3-4実装 (優先度: 中)

    • Partial Release無効化ENV経由で即テスト可能
    • Cache容量チューニング
  4. 統合テスト

    # すべての最適化を統合
    make bench_random_mixed_hakmem
    ./bench_random_mixed_hakmem 10000000 400 1  # 10M iterations
    # 目標: 70-100M ops/smimalloc並み
    

参考資料

ソースコード参照

  • ベンチマークコード: /mnt/workdisk/public_share/hakmem/bench_random_mixed.c
  • SuperSlabアロケーション: /mnt/workdisk/public_share/hakmem/core/hakmem_tiny_superslab.c
  • Partial Release: /mnt/workdisk/public_share/hakmem/core/hakmem_tiny_intel.inc:685-698
  • Tiny設定: /mnt/workdisk/public_share/hakmem/core/hakmem_tiny_config.h
  • madviseバッチ処理: /mnt/workdisk/public_share/hakmem/core/hakmem_batch.c

測定コマンド

# スループット測定
./bench_random_mixed_hakmem 1000000 400 1
./bench_random_mixed_mi 1000000 400 1
./bench_random_mixed_system 1000000 400 1

# ページフォルト測定
perf stat -e page-faults,cycles ./bench_random_mixed_hakmem 1000000 400 1

# プロファイリング
perf record -F 99 -g -e cycles,page-faults ./bench_random_mixed_hakmem 1000000 400 1
perf report --stdio

# Partial Release無効化テスト
HAKMEM_TINY_SS_PARTIAL=0 ./bench_random_mixed_hakmem 1000000 400 1

Linux カーネル機能

  • mmap(2): man 2 mmap
  • madvise(2): man 2 madvise
  • MAP_POPULATE: Linux 2.5.46以降で利用可能
  • MADV_WILLNEED: ページプリフェッチヒント
  • MADV_DONTNEED: ページ解放(次回アクセス時にページフォルト)
  • MADV_FREE: Linux 4.5以降、MADV_DONTNEEDより軽量

まとめ

主要発見

  1. HAKMEMは16-1024Bサイズ範囲でmimallocの1/25の性能4.8M vs 120M ops/s
  2. 根本原因はページフォルト6,762 vs 146 = 46倍
  3. 実行時間の86%がカーネル時間madvise()とページフォルト処理)
  4. Partial Release機能の影響は軽微わずか2%の改善)
  5. MAP_POPULATEで5-10倍の改善が期待できる(即効性が高い)

次のアクション

即座に実施すべき最適化:

  1. MAP_POPULATEの追加期待: 5-10倍改善
  2. Partial Releaseのデフォルト無効化期待: 2%改善)

中期的な最適化: 3. SuperSlabサイズのチューニング期待: 1.5-2倍改善 4. Cache容量の最適化期待: 1.2-1.5倍改善)

長期的な検討: 5. SuperSlabアーキテクチャの根本的見直しより小さい粒度のメモリ管理 6. madvise()の完全排除またはバックグラウンド化

目標達成の見通し:

  • Phase 1-2実装で7.5-15倍の改善36-72M ops/sが現実的
  • mimalloc並み120M ops/sを達成するには、さらなるアーキテクチャ改善が必要

報告者: Claude (Anthropic) 分析日時: 2025年12月5日 測定環境: Linux 6.8.0-87-generic, x86_64