Key findings from 2025-12-05 session: 1. HAKMEM vs mimalloc: 27x slower (4.5M vs 122M ops/s) 2. Root cause investigation: madvise 1081 calls vs mimalloc 0 calls 3. madvise disable test: -15% performance (worse, not better!) 4. Conclusion: MADV_POPULATE_WRITE is actually helping, not hurting 5. ChatGPT was right: time to move to user-space optimization phase Reports added: - WORKLOAD_COMPARISON_20251205.md - PARTIAL_RELEASE_INVESTIGATION_REPORT_20251205.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
11 KiB
11 KiB
Partial Release無効化テスト結果レポート
調査日: 2025年12月5日 目的: madvise()がCPU時間の58%を占めている問題を解決するため、Partial Release機能の影響を調査
📊 測定結果サマリー
ベンチマーク性能比較
| 条件 | Run1 (ops/s) | Run2 (ops/s) | Run3 (ops/s) | 平均 (ops/s) | 改善率 |
|---|---|---|---|---|---|
| Partial Release有効 (baseline) | 4,784,048 | 4,709,849 | 4,739,815 | 4,744,571 | - |
| Partial Release無効 (HAKMEM_TINY_SS_PARTIAL=0) | 4,920,803 | 4,778,194 | 4,889,397 | 4,862,798 | +2.5% |
perf統計比較
| 指標 | Partial Release有効 | Partial Release無効 | 差 |
|---|---|---|---|
| cycles | 1,164,151,908 | 1,160,123,269 | -4M (-0.3%) |
| page-faults | 6,759 | 6,760 | +1 (変化なし) |
| cache-misses | 8,210,925 | 8,142,282 | -69K (-0.8%) |
| L1-dcache-load-misses | 17,780,041 (3.99%) | 17,624,374 (3.96%) | -156K (-0.9%) |
| user時間 | 0.030s | 0.035s | +0.005s |
| sys時間 | 0.247s | 0.252s | +0.005s |
| 実行時間 | 0.278s | 0.288s | +0.010s |
🔍 重大な発見:Partial Release無効化してもmadvise()は減らない!
strace syscall統計
HAKMEM_TINY_SS_PARTIAL=0(Partial Release無効)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
91.21 0.173846 160 1082 madvise
5.30 0.010094 9 1092 munmap
3.20 0.006106 5 1113 mmap
デフォルト(Partial Release有効)
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
91.86 0.159657 147 1081 madvise
4.96 0.008628 7 1091 munmap
2.90 0.005043 4 1113 mmap
結論: madvise呼び出し回数はほぼ同じ(1081-1082回)!
🔬 詳細調査:madvise()の発生源を特定
発見した環境変数
| 環境変数名 | デフォルト値 | 用途 | 効果 |
|---|---|---|---|
| HAKMEM_TINY_SS_PARTIAL | 1 (有効) | SuperSlabのPartial Release制御 | 効果なし (2.5%改善は誤差範囲) |
| HAKMEM_TINY_SS_PARTIAL_INTERVAL | 4 | Partial Release実行間隔 | - |
| HAKMEM_DISABLE_BATCH | 0 (有効) | Batch madvise制御 | 効果なし |
| HAKMEM_SS_PREFAULT | 0 (OFF) | SuperSlab prefault制御 | 効果なし |
全機能無効化テスト
HAKMEM_DISABLE_BATCH=1 HAKMEM_TINY_SS_PARTIAL=0 HAKMEM_SS_PREFAULT=0 strace -c ./bench_random_mixed_hakmem 1000000 400 1
結果: 依然として1081回のmadvise呼び出し (91.74% of syscall time)
💡 根本原因の特定
madvise()呼び出し箇所の完全マップ
straceで実際の呼び出しを確認:
strace -e madvise ./bench_random_mixed_hakmem 1000000 400 1
全てのmadvise呼び出しは MADV_POPULATE_WRITE であることが判明!
madvise(0x73f0e2680000, 524288, MADV_POPULATE_WRITE) = 0
madvise(0x73f0e2300000, 524288, MADV_POPULATE_WRITE) = 0
madvise(0x73f0e2200000, 524288, MADV_POPULATE_WRITE) = 0
...
発生源コード
1. 主犯: core/superslab_cache.c (Fallback Path)
/mnt/workdisk/public_share/hakmem/core/superslab_cache.c:113
// Pre-fault pages in fallback path (only after trim to actual SuperSlab size)
// This is critical: we MUST touch the pages after munmap() to establish valid mappings
// CRITICAL FIX (2025-12-05): Use MADV_POPULATE_WRITE for efficiency
#ifdef MADV_POPULATE_WRITE
int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE); // ← ここが主犯!
if (ret != 0) {
// Fallback: explicit memset
memset(ptr, 0, ss_size);
}
#else
// Fallback for kernels < 5.14
memset(ptr, 0, ss_size);
#endif
問題点:
- この
madvise()は環境変数で無効化できない - Fallback pathが常に使われている(MAP_ALIGNED_SUPERが失敗?)
- munmap trim後に必ずMADV_POPULATE_WRITEが実行される
2. core/box/ss_os_acquire_box.c:179 (populate=1の場合)
#ifdef MADV_POPULATE_WRITE
if (populate) {
int ret = madvise(ptr, ss_size, MADV_POPULATE_WRITE);
...
}
#endif
3. core/hakmem_tiny_intel.inc:692 (Partial Release - 今回無効化を試した箇所)
static inline void superslab_partial_release(SuperSlab* ss, uint32_t epoch) {
#if defined(MADV_DONTNEED)
if (!g_ss_partial_enable) return; // ← HAKMEM_TINY_SS_PARTIAL=0で無効化
...
if (madvise(ss, len, MADV_DONTNEED) == 0) {
ss->partial_epoch = epoch;
}
#endif
}
重要: この関数は今回のベンチマークではほとんど呼ばれていない!
4. core/hakmem_batch.c:120,123 (Batch madvise)
int ret = madvise(ptr, size, MADV_FREE);
if (ret != 0) {
ret = madvise(ptr, size, MADV_DONTNEED);
}
📈 アーキテクチャ分析
madvise()の4つの用途
| # | 場所 | 種類 | 目的 | 頻度 | 環境変数制御 |
|---|---|---|---|---|---|
| 1 | superslab_cache.c:113 |
MADV_POPULATE_WRITE | Fallback path でのページ確保 | 超高頻度 (~1000回) | ❌ 不可 |
| 2 | ss_os_acquire_box.c:179 |
MADV_POPULATE_WRITE | populate=1時のprefault | populate時のみ | HAKMEM_SS_PREFAULT |
| 3 | hakmem_tiny_intel.inc:692 |
MADV_DONTNEED | Partial Release (空SuperSlabのメモリ返却) | 低頻度 | ✅ HAKMEM_TINY_SS_PARTIAL |
| 4 | hakmem_batch.c:120 |
MADV_FREE/DONTNEED | Batch madvise (大きいalloc解放時) | 中頻度 | ✅ HAKMEM_DISABLE_BATCH |
今回のベンチマークでの実態
- 1081回のmadvise呼び出し全てが
MADV_POPULATE_WRITE - これは**#1のFallback Path**から発生している
- #3 Partial Release (MADV_DONTNEED) は今回のベンチマークではほとんど実行されていない
🎯 結論
主要な発見
-
HAKMEM_TINY_SS_PARTIAL=0は効果がほぼない(+2.5%程度)- 理由: このベンチマークではPartial Releaseがほとんど発動していない
- 1081回のmadviseのうち、MADV_DONTNEEDはほぼゼロ
-
真の問題は
superslab_cache.cのFallback Path- 全てのSuperSlab割り当てで MADV_POPULATE_WRITE が実行される
- 環境変数では制御不可能
- コメントによると「munmap trim後の再確保に必須」
-
前回のperf分析での58%のmadvise時間は別の要因だった可能性
- 前回のベンチマーク:
bench_random_mixed.c(16-1024バイト) - 今回のベンチマーク:
bench_random_mixed_hakmem 1000000 400 1(16-1040バイト) - ワークロードが異なるため、Partial Releaseの発動頻度も異なる
- 前回のベンチマーク:
-
性能への影響は限定的
- MADV_POPULATE_WRITEは割り当て時の1回のみ(ホットパスではない)
- ウォームアップ後はSuperSlabの再利用で新規madviseは減る
- syscall時間の91%でも、全体の実行時間は0.25s程度
🔧 推奨される次のステップ
Option 1: Fallback Path の最適化(根本対策)
問題: MAP_ALIGNED_SUPERが失敗し、常にFallback Pathが使われている
調査項目:
- なぜMAP_ALIGNED_SUPERが失敗するのか?
- FreeBSDでは成功するが、Linuxでは失敗する?
- Fallback PathでのMADV_POPULATE_WRITEは本当に必須か?
改善案:
// Option A: 環境変数で制御可能にする
static int prefault_disabled = -1;
if (prefault_disabled == -1) {
const char* env = getenv("HAKMEM_NO_FALLBACK_PREFAULT");
prefault_disabled = (env && *env && *env != '0') ? 1 : 0;
}
if (!prefault_disabled) {
madvise(ptr, ss_size, MADV_POPULATE_WRITE);
}
// Option B: memsetフォールバックを常に使う(syscall削減)
memset(ptr, 0, ss_size); // syscallなし、ただしCPU使用量増加
Option 2: ワークロード別の最適化
異なるベンチマークで再測定:
# より長時間のベンチマーク(Partial Releaseが発動しやすい)
./bench_random_mixed_hakmem 10000000 400 1
# より大きいワーキングセット(SuperSlabの入れ替えが多い)
./bench_random_mixed_hakmem 1000000 4000 1
Option 3: perf record で詳細プロファイリング
前回報告されていた58%のmadviseを再現:
# 前回と同じベンチマーク条件で測定
perf record -g ./bench_random_mixed.c の該当コマンド
perf report --stdio
📋 コード参照
関連ファイル
/mnt/workdisk/public_share/hakmem/core/superslab_cache.c:113- MADV_POPULATE_WRITE (主犯)/mnt/workdisk/public_share/hakmem/core/box/ss_os_acquire_box.c:179- MADV_POPULATE_WRITE (populate時)/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_intel.inc:685-698- superslab_partial_release (MADV_DONTNEED)/mnt/workdisk/public_share/hakmem/core/hakmem_batch.c:120- Batch madvise (MADV_FREE/DONTNEED)/mnt/workdisk/public_share/hakmem/core/hakmem_config.c:231-234- HAKMEM_DISABLE_BATCH の処理/mnt/workdisk/public_share/hakmem/core/hakmem_tiny_lifecycle.inc:78-90- HAKMEM_TINY_SS_PARTIAL の処理/mnt/workdisk/public_share/hakmem/core/box/ss_prefault_box.h:35-54- HAKMEM_SS_PREFAULT の処理
🧪 再現手順
環境変数一覧
# Partial Release無効化(効果: +2.5%程度)
export HAKMEM_TINY_SS_PARTIAL=0
# Batch madvise無効化(効果: なし)
export HAKMEM_DISABLE_BATCH=1
# SuperSlab prefault無効化(効果: なし)
export HAKMEM_SS_PREFAULT=0
ベンチマーク実行
# ベースライン
./bench_random_mixed_hakmem 1000000 400 1
# Partial Release無効
HAKMEM_TINY_SS_PARTIAL=0 ./bench_random_mixed_hakmem 1000000 400 1
# 全無効化
HAKMEM_DISABLE_BATCH=1 HAKMEM_TINY_SS_PARTIAL=0 HAKMEM_SS_PREFAULT=0 ./bench_random_mixed_hakmem 1000000 400 1
# syscall詳細
strace -c ./bench_random_mixed_hakmem 1000000 400 1
strace -e madvise ./bench_random_mixed_hakmem 1000000 400 1 2>&1 | head -30
📚 参考資料
PERF_ANALYSIS_16_1024B_20251205.md- 前回のperf分析(madvise 58%問題)LAZY_ZEROING_IMPLEMENTATION_RESULTS_20251204.md- MADV_DONTNEED最適化の失敗例EXPLICIT_PREFAULT_IMPLEMENTATION_REPORT_20251205.md- MADV_POPULATE_WRITE導入の経緯
報告者: Claude Code 最終更新: 2025年12月5日