put_many 4N→2N 락 최적화 벤치마크
변경 개요
5a27732f ([Perf][RawBlock] Reduce put_many lock count from 4N to 2N) 가 _write_one 내부에서 잡던
_inflight_io_count ±1 락 2개를 put_many 의 allocate-lock / commit-lock 으로 흡수.
명목상 키당 4회 → 2회 락 절약.
벤치마크 설계
- 파일:
benchmarks/storage_backend_io/bench_put_many_lock_patterns.py - 2N 패턴: 현재 코드 그대로
- 4N 시뮬레이션:
_write_onemonkeypatch — 원본 호출 전후에with self._lock: dummy ±12회 추가 - 조건: N=[10, 100, 1000] × latency=[0, 10, 50] µs (pwrite sleep), warmup=3, iters=10
- 환경:
/home/ny/LMCache(fake in-memory device, Python 3.12)
결과
N latency 2N median 4N median diff lock%
────────────────────────────────────────────────────────────
10 0 µs 0.077 ms 0.080 ms +3.9% 3.8%
10 10 µs 1.296 ms 1.364 ms +5.3% 5.0% (sleep 노이즈)
10 50 µs 2.581 ms 2.623 ms +1.6% 1.6%
100 0 µs 1.237 ms 0.735 ms -40.6% -- (GC/JIT 이상치)
100 10 µs 17.977 ms 18.650 ms +3.7% 3.6% (sleep 노이즈)
100 50 µs 26.025 ms 25.720 ms -1.2% -- (noise)
1000 0 µs 7.271 ms 7.463 ms +2.6% 2.6%
1000 10 µs 183.928 ms 187.898 ms +2.2% 2.1%
1000 50 µs 262.893 ms 263.900 ms +0.4% 0.4%
diff = (4N − 2N) / 2N × 100
N=100 lat=0 결과는 2N 측정 중 GC 인터럽트로 인한 이상치.
lat=10µs 계열은 OS sleep 최소 단위(~100µs)보다 작아 노이즈 큼.
신뢰할 수 있는 값: N=1000 lat=0 (순수 CPU) / N=1000 lat=50µs (NVMe급).
해석
| 구간 | 이득 | 판단 |
|---|---|---|
| lat=0µs (순수 CPU) | 실재하지만 작음 | |
| lat=50µs (빠른 NVMe) | <1% | 측정 노이즈와 구별 불가 |
| lat≥100µs (일반 NVMe/SATA) | ≪1% | 사실상 0 |
락 자체의 소유 시간이 매우 짧아(_inflight_io_count ±1 정수 연산 수십 ns),
실제 I/O 지연이 조금만 있어도 절약 효과가 묻힘.
부작용 (코드 리뷰에서 확인된 것)
| # | 내용 | 심각도 |
|---|---|---|
| F1 | _last_io_ts 가 prep 실패 시에도 갱신 → checkpoint idle gate 회귀 | high |
| F3+F11 | inflight_io_count() ON window 가 docstring 보다 넓어짐 | medium |
| F5 | _CountingLock.__enter__ 반환값 버그 (latent) | low |
F1/F5 는 브랜치에서 수정 완료, 하지만 이 수정들이 필요했다는 것 자체가 최적화의 순이익이 작음을 보여줌.
결정
PR drop. 실측 이득 <1%(NVMe 현실 구간) 대비 semantic 변화(inflight window 확장)와
코드 복잡도 증가 비용이 크다. raw-block-perf-findings.md §2-1, §2-2 의 lock 항목은
여전히 open (이 PR 이 해결한 것으로 보지 않음).