expH fix: write test file with F_NOCACHE — first run measured page cache, not SSD
F_NOCACHE on a read fd does not bypass already-resident pages; writing the test file normally made every page resident, so 'cold' cells hit unified memory (42-115 GB/s, impossible for NVMe). Also recreate the file before the GPU-load phase since the warm phase repopulates cache. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 1 changed file with +21 and −8
modified
experiments/micro/expH_ssd_feasibility/benchmark.py
+21 −8
@@ -48,22 +48,31 @@ THREAD_COUNTS = [1, 4, 8] | ||
| 48 | 48 | |
| 49 | 49 | |
| 50 | 50 | def create_test_file(path: Path, size_bytes: int) -> None: |
| 51 | − """Write an incompressible test file (APFS compresses nothing here, but | |
| 52 | − avoid all-zero data so no transparent optimization can help).""" | |
| 53 | − if path.exists() and path.stat().st_size == size_bytes: | |
| 54 | − return | |
| 51 | + """Write an incompressible test file WITHOUT populating the page cache. | |
| 52 | + | |
| 53 | + Crucial macOS detail (discovered in the first run of this experiment): | |
| 54 | + F_NOCACHE on a *read* fd does not bypass pages already resident in the | |
| 55 | + unified page cache — and writing the file normally makes every page | |
| 56 | + resident. The file must therefore be written with F_NOCACHE set on the | |
| 57 | + write fd, so 'cold' reads afterwards genuinely hit the SSD. | |
| 58 | + """ | |
| 59 | + if path.exists(): | |
| 60 | + path.unlink() # always recreate: an old file may have cached pages | |
| 55 | 61 | import numpy as np |
| 56 | 62 | |
| 57 | 63 | rng = np.random.default_rng(42) |
| 58 | 64 | chunk = rng.integers(0, 256, size=64 << 20, dtype=np.uint8).tobytes() |
| 59 | 65 | written = 0 |
| 60 | − with open(path, "wb") as f: | |
| 66 | + fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o644) | |
| 67 | + try: | |
| 68 | + fcntl.fcntl(fd, F_NOCACHE, 1) | |
| 61 | 69 | while written < size_bytes: |
| 62 | 70 | n = min(len(chunk), size_bytes - written) |
| 63 | − f.write(chunk[:n]) | |
| 71 | + os.write(fd, chunk[:n]) | |
| 64 | 72 | written += n |
| 65 | − f.flush() | |
| 66 | − os.fsync(f.fileno()) | |
| 73 | + os.fsync(fd) | |
| 74 | + finally: | |
| 75 | + os.close(fd) | |
| 67 | 76 | |
| 68 | 77 | |
| 69 | 78 | def _reader(path: Path, offsets: list[int], block: int, nocache: bool, |
@@ -228,6 +237,10 @@ def main() -> None: | ||
| 228 | 237 | gpu = GpuLoad() |
| 229 | 238 | gpu_iters = 0 |
| 230 | 239 | if gpu.available: |
| 240 | + # the warm-cache phase above populated the page cache; recreate the | |
| 241 | + # file (uncached write) so the GPU-load cells are genuinely cold | |
| 242 | + print("recreating test file to evict cached pages before GPU-load phase…", flush=True) | |
| 243 | + create_test_file(test_file, file_size) | |
| 231 | 244 | print("re-running key cells under concurrent Metal (MLX) matmul load…", flush=True) |
| 232 | 245 | with gpu: |
| 233 | 246 | run_cells(gpu_loaded=True) |
| 234 | 247 | |