261 lines
7.0 KiB
Markdown
261 lines
7.0 KiB
Markdown
|
|
# Benchmarking Checklist - 二度と間違えないために
|
|||
|
|
|
|||
|
|
## ❌ **何が起きたか(2025-10-26 事件)**
|
|||
|
|
|
|||
|
|
### 問題
|
|||
|
|
Phase 1-4.1 の全てのベンチマークで **glibc malloc** を測定していた。
|
|||
|
|
hakmem の性能を測定していると思い込んでいたが、実際は system allocator だった。
|
|||
|
|
|
|||
|
|
### 発覚の経緯
|
|||
|
|
1. Phase 3: 391 M ops/sec と報告
|
|||
|
|
2. Phase 4: 373 M ops/sec に退行
|
|||
|
|
3. Phase 4.1: 381 M ops/sec に改善
|
|||
|
|
4. Phase 4.2 で Makefile を修正して hakmem を正しくリンク
|
|||
|
|
5. **103 M ops/sec** という衝撃の結果
|
|||
|
|
6. → 全ての測定が glibc malloc だったと判明
|
|||
|
|
|
|||
|
|
### 根本原因
|
|||
|
|
```makefile
|
|||
|
|
# 以前(ターゲットなし)
|
|||
|
|
make bench_tiny
|
|||
|
|
↓
|
|||
|
|
# Makefileの暗黙ルール
|
|||
|
|
gcc bench_tiny.c -o bench_tiny # hakmem とリンクせず!
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
bench_tiny.c は単に `malloc/free` を呼ぶだけなので、system allocator が使われた。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ **二度と起こらないための対策**
|
|||
|
|
|
|||
|
|
### 1. Makefile の明示的なターゲット(完了)
|
|||
|
|
|
|||
|
|
```makefile
|
|||
|
|
# Phase 4: Tiny Pool benchmarks (properly linked with hakmem)
|
|||
|
|
TINY_BENCH_OBJS = hakmem.o hakmem_config.o ... (全ての hakmem オブジェクト)
|
|||
|
|
|
|||
|
|
bench_tiny: bench_tiny.o $(TINY_BENCH_OBJS)
|
|||
|
|
$(CC) -o $@ $^ $(LDFLAGS)
|
|||
|
|
@echo "✓ bench_tiny built with hakmem" # 明確なメッセージ
|
|||
|
|
|
|||
|
|
bench_tiny_mt: bench_tiny_mt.o $(TINY_BENCH_OBJS)
|
|||
|
|
$(CC) -o $@ $^ $(LDFLAGS)
|
|||
|
|
@echo "✓ bench_tiny_mt built with hakmem"
|
|||
|
|
|
|||
|
|
test_mf2: test_mf2.o $(TINY_BENCH_OBJS)
|
|||
|
|
$(CC) -o $@ $^ $(LDFLAGS)
|
|||
|
|
@echo "✓ test_mf2 built with hakmem"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. ベンチマーク実行前のチェックリスト
|
|||
|
|
|
|||
|
|
#### **Step 1: ビルド確認**
|
|||
|
|
```bash
|
|||
|
|
# 必ず make clean してから
|
|||
|
|
make clean && make bench_tiny
|
|||
|
|
|
|||
|
|
# ビルドメッセージを確認
|
|||
|
|
# 期待: "✓ bench_tiny built with hakmem"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### **Step 2: リンク確認**
|
|||
|
|
```bash
|
|||
|
|
# 方法 A: シンボル確認(推奨)
|
|||
|
|
nm ./bench_tiny | grep hak_tiny_alloc
|
|||
|
|
# 期待: hak_tiny_alloc のシンボルが見つかる
|
|||
|
|
# NG: "no symbols" または何も出ない
|
|||
|
|
|
|||
|
|
# 方法 B: ldd 確認(共有ライブラリの場合)
|
|||
|
|
ldd ./bench_tiny
|
|||
|
|
# hakmem が静的リンクされているので libhakmem.so は出ないが、
|
|||
|
|
# libc.so, libpthread.so, libm.so が出ることを確認
|
|||
|
|
|
|||
|
|
# 方法 C: size 確認
|
|||
|
|
size ./bench_tiny
|
|||
|
|
# 期待: text サイズが大きい(hakmem コードが含まれる)
|
|||
|
|
# NG: text サイズが数KB程度(bench_tiny.c のみ)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**正しい例**:
|
|||
|
|
```bash
|
|||
|
|
$ nm ./bench_tiny | grep hak_tiny_alloc
|
|||
|
|
000000000000b2a0 T hak_tiny_alloc
|
|||
|
|
000000000000cd10 T hak_tiny_alloc_superslab
|
|||
|
|
|
|||
|
|
$ size ./bench_tiny
|
|||
|
|
text data bss dec hex filename
|
|||
|
|
89234 3456 12800 105490 19c12 bench_tiny
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**間違いの例(glibc malloc を使っている)**:
|
|||
|
|
```bash
|
|||
|
|
$ nm ./bench_tiny | grep hak_tiny_alloc
|
|||
|
|
# 何も出ない!
|
|||
|
|
|
|||
|
|
$ size ./bench_tiny
|
|||
|
|
text data bss dec hex filename
|
|||
|
|
2345 128 64 2537 9e9 bench_tiny
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### **Step 3: 実行前の sanity check**
|
|||
|
|
```bash
|
|||
|
|
# hakmem 初期化メッセージを確認(HAKMEM_DEBUG_VERBOSE=1 の場合)
|
|||
|
|
# または stats 出力で hakmem が動いているか確認
|
|||
|
|
./bench_tiny 2>&1 | head -20
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3. ベンチマークスクリプトの標準化
|
|||
|
|
|
|||
|
|
**推奨**: `make bench_tiny` を使う(Makefile で正しく設定済み)
|
|||
|
|
|
|||
|
|
**非推奨**: 手動で gcc を叩く(リンク漏れのリスク)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 4. ドキュメント化されたワークフロー
|
|||
|
|
|
|||
|
|
#### **標準ベンチマーク手順**
|
|||
|
|
```bash
|
|||
|
|
# 1. Clean build
|
|||
|
|
make clean
|
|||
|
|
|
|||
|
|
# 2. Build with verification
|
|||
|
|
make bench_tiny 2>&1 | tee build.log
|
|||
|
|
grep "✓ bench_tiny built with hakmem" build.log || echo "ERROR: hakmem not linked!"
|
|||
|
|
|
|||
|
|
# 3. Verify linkage
|
|||
|
|
nm ./bench_tiny | grep hak_tiny_alloc || echo "ERROR: hakmem symbols not found!"
|
|||
|
|
|
|||
|
|
# 4. Run benchmark (3 times for stability)
|
|||
|
|
for i in {1..3}; do
|
|||
|
|
echo "=== Run $i/3 ==="
|
|||
|
|
./bench_tiny 2>&1 | tail -6
|
|||
|
|
echo ""
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
# 5. Compare with baseline
|
|||
|
|
# (save results and compare)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 5. 自動化された検証
|
|||
|
|
|
|||
|
|
#### **verify_bench.sh**(作成推奨)
|
|||
|
|
```bash
|
|||
|
|
#!/bin/bash
|
|||
|
|
# verify_bench.sh - Verify benchmark binary is correctly linked
|
|||
|
|
|
|||
|
|
BINARY="$1"
|
|||
|
|
if [ ! -f "$BINARY" ]; then
|
|||
|
|
echo "❌ Binary not found: $BINARY"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Check for hakmem symbols
|
|||
|
|
if nm "$BINARY" | grep -q "hak_tiny_alloc"; then
|
|||
|
|
echo "✅ hakmem symbols found"
|
|||
|
|
else
|
|||
|
|
echo "❌ hakmem symbols NOT found - using system allocator!"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Check binary size (heuristic)
|
|||
|
|
TEXT_SIZE=$(size "$BINARY" | awk 'NR==2 {print $1}')
|
|||
|
|
if [ "$TEXT_SIZE" -gt 50000 ]; then
|
|||
|
|
echo "✅ Binary size OK ($TEXT_SIZE bytes)"
|
|||
|
|
else
|
|||
|
|
echo "⚠️ Binary size suspiciously small ($TEXT_SIZE bytes)"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
echo "✅ Verification passed: $BINARY"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使用例**:
|
|||
|
|
```bash
|
|||
|
|
chmod +x verify_bench.sh
|
|||
|
|
./verify_bench.sh ./bench_tiny
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 **真の性能データ(Phase 4.2 で初めて正しく測定)**
|
|||
|
|
|
|||
|
|
### System malloc (glibc)
|
|||
|
|
- **Throughput**: ~370 M ops/sec
|
|||
|
|
- **Latency**: 2.7 ns/op
|
|||
|
|
|
|||
|
|
### hakmem (Phase 4.2)
|
|||
|
|
- **Throughput**: **103 M ops/sec**
|
|||
|
|
- **Latency**: **9.6 ns/op**
|
|||
|
|
- **Gap**: glibc が **3.6倍速い**
|
|||
|
|
|
|||
|
|
### mimalloc (推定)
|
|||
|
|
- **Latency**: 14 ns/op (ANALYSIS_SUMMARY.md)
|
|||
|
|
- **Throughput**: ~71 M ops/sec
|
|||
|
|
- **Note**: hakmem の方が速い可能性がある
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔄 **今後のベンチマーク protocol**
|
|||
|
|
|
|||
|
|
### Before every benchmark:
|
|||
|
|
1. ✅ `make clean`
|
|||
|
|
2. ✅ `make bench_tiny` (ビルドメッセージ確認)
|
|||
|
|
3. ✅ `nm ./bench_tiny | grep hak_tiny_alloc` (シンボル確認)
|
|||
|
|
4. ✅ Run benchmark 3 times
|
|||
|
|
5. ✅ Compare with baseline
|
|||
|
|
|
|||
|
|
### After benchmark:
|
|||
|
|
1. ✅ Record exact commit hash
|
|||
|
|
2. ✅ Record build command
|
|||
|
|
3. ✅ Record verification result
|
|||
|
|
4. ✅ Save raw data
|
|||
|
|
|
|||
|
|
### Before claiming performance change:
|
|||
|
|
1. ✅ Verify > 5% difference
|
|||
|
|
2. ✅ Run 5+ times for statistical significance
|
|||
|
|
3. ✅ Check for system noise (other processes)
|
|||
|
|
4. ✅ Document what changed
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 **Lessons Learned**
|
|||
|
|
|
|||
|
|
1. **Make の暗黙ルールは危険** - 明示的なターゲットを常に定義
|
|||
|
|
2. **"動く" ≠ "正しい"** - malloc/free は常に動く(system allocator がある)
|
|||
|
|
3. **測定前の検証が必須** - nm/ldd/size でリンク確認
|
|||
|
|
4. **性能変化には根拠が必要** - コード変更と性能の因果関係を確認
|
|||
|
|
5. **ドキュメント化** - チェックリストがないと同じ間違いを繰り返す
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 **Action Items**
|
|||
|
|
|
|||
|
|
- [x] Makefile に明示的ターゲット追加
|
|||
|
|
- [x] BENCHMARKING_CHECKLIST.md 作成
|
|||
|
|
- [ ] verify_bench.sh スクリプト作成(オプション)
|
|||
|
|
- [ ] BENCHMARK_PROTOCOL.md 作成(詳細手順)
|
|||
|
|
- [ ] Phase 0 (Baseline) の性能を測定し直す
|
|||
|
|
- [ ] Phase 1-4 の性能を正しく測定し直す
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚫 **絶対にやってはいけないこと**
|
|||
|
|
|
|||
|
|
1. ❌ `make bench_tiny` なしで手動コンパイル
|
|||
|
|
2. ❌ リンク確認なしでベンチマーク実行
|
|||
|
|
3. ❌ 1回だけの測定で結論
|
|||
|
|
4. ❌ 性能変化の原因を確認せずにコミット
|
|||
|
|
5. ❌ ベースラインなしで「改善」を主張
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ **今後は絶対に間違えない**
|
|||
|
|
|
|||
|
|
このチェックリストに従えば、二度と同じ間違いは起きませんにゃ!🐱✨
|