Skip to content

Commit e02fb66

Browse files
committed
bench(pathfinder): add discovery latency suite
Signed-off-by: Kevin Turcios <turcioskevinr@gmail.com>
1 parent 9d34aaf commit e02fb66

8 files changed

Lines changed: 1189 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
results-*.json
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# cuda.pathfinder benchmarks
2+
3+
Read the README.md in this directory for benchmark scope and usage.
4+
5+
Keep benchmark fixtures outside timed loops. Verify benchmark behavior against
6+
the source in `../../cuda_pathfinder` and preserve support for Linux and Windows.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# cuda.pathfinder benchmarks
2+
3+
These benchmarks measure filesystem-discovery latency in `cuda.pathfinder`.
4+
They exercise the in-tree source package and do not require a GPU or CUDA
5+
Toolkit.
6+
7+
The suite uses temporary directory trees prepared before timing starts. It has
8+
two levels of coverage:
9+
10+
- `find_sub_dirs.*` isolates the underlying filesystem mechanism with realistic
11+
one-root and three-root cases, plus wildcard and cache diagnostics.
12+
- `public_discovery.*` measures cold-cache calls through the public header,
13+
binary, and static-library APIs. Fixtures use real NVIDIA wheel layouts such
14+
as `nvidia/cuda_runtime/include` and `nvidia/cuda_nvcc/bin`.
15+
16+
The public cold-cache benchmarks clear pathfinder's process-lifetime caches on
17+
each iteration. This intentionally models first use in a fresh process; the
18+
small cache-clear cost is included in the reported time. Three roots represent
19+
a virtual environment, user site-packages, and system site-packages, with the
20+
target in the last root to measure the complete search order.
21+
22+
## Usage
23+
24+
Requires pixi. The `source` environment installs `cuda-pathfinder` from this
25+
checkout and works on Linux and Windows.
26+
27+
```bash
28+
# List benchmark IDs.
29+
pixi run -e source bench --list
30+
31+
# Quick functional validation; timings are not meaningful.
32+
pixi run -e source bench-smoke-test
33+
34+
# Run the full suite and write results-python.json.
35+
pixi run -e source bench
36+
37+
# Reduce runtime while iterating.
38+
pixi run -e source bench --min-time 0.1
39+
```
40+
41+
## Comparing changes
42+
43+
Save results from the base revision and the modified checkout under distinct
44+
names, then use pyperf's statistical comparison:
45+
46+
```bash
47+
pixi run -e source bench -o results-before.json
48+
pixi run -e source bench -o results-after.json
49+
pixi run -e source -- python -m pyperf compare_to \
50+
results-before.json results-after.json --table
51+
```
52+
53+
For stable results, minimize other system activity and use the same machine,
54+
Python version, and Pixi environment for both runs. See pyperf's system tuning
55+
guidance: https://pyperf.readthedocs.io/en/latest/system.html#system
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import atexit
6+
import tempfile
7+
import time
8+
from pathlib import Path
9+
10+
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs, find_sub_dirs_cached, find_sub_dirs_no_cache
11+
12+
_WILDCARD_CHILD_COUNT = 50
13+
_SUB_DIRS = ("nvidia", "cuda_runtime", "lib")
14+
_MISSING_SUB_DIRS = ("nvidia", "not_installed", "lib")
15+
16+
_TEMP_DIR = tempfile.TemporaryDirectory()
17+
_ROOT = Path(_TEMP_DIR.name)
18+
_PARENTS: tuple[str, ...] = tuple(str(_ROOT / f"environment-{index}") for index in range(3))
19+
20+
for parent in _PARENTS:
21+
(Path(parent) / Path(*_SUB_DIRS)).mkdir(parents=True)
22+
23+
_WILDCARD_PARENT = _ROOT / "wildcard-environment"
24+
for index in range(_WILDCARD_CHILD_COUNT):
25+
(_WILDCARD_PARENT / "nvidia" / f"package-{index}" / "lib").mkdir(parents=True)
26+
27+
_WILDCARD_PARENTS = (str(_WILDCARD_PARENT),)
28+
_WILDCARD_SUB_DIRS = ("nvidia", "*", "lib")
29+
30+
find_sub_dirs_cached.cache_clear()
31+
find_sub_dirs(_PARENTS, _SUB_DIRS)
32+
33+
34+
def bench_exact_hit_1_parent(loops: int) -> float:
35+
_fn = find_sub_dirs_no_cache
36+
_parents = _PARENTS[:1]
37+
_sub_dirs = _SUB_DIRS
38+
39+
t0 = time.perf_counter()
40+
for _ in range(loops):
41+
_fn(_parents, _sub_dirs)
42+
return time.perf_counter() - t0
43+
44+
45+
def bench_exact_hit_3_parents(loops: int) -> float:
46+
_fn = find_sub_dirs_no_cache
47+
_parents = _PARENTS
48+
_sub_dirs = _SUB_DIRS
49+
50+
t0 = time.perf_counter()
51+
for _ in range(loops):
52+
_fn(_parents, _sub_dirs)
53+
return time.perf_counter() - t0
54+
55+
56+
def bench_exact_miss_3_parents(loops: int) -> float:
57+
_fn = find_sub_dirs_no_cache
58+
_parents = _PARENTS
59+
_sub_dirs = _MISSING_SUB_DIRS
60+
61+
t0 = time.perf_counter()
62+
for _ in range(loops):
63+
_fn(_parents, _sub_dirs)
64+
return time.perf_counter() - t0
65+
66+
67+
def bench_wildcard_hit_50_children(loops: int) -> float:
68+
_fn = find_sub_dirs_no_cache
69+
_parents = _WILDCARD_PARENTS
70+
_sub_dirs = _WILDCARD_SUB_DIRS
71+
72+
t0 = time.perf_counter()
73+
for _ in range(loops):
74+
_fn(_parents, _sub_dirs)
75+
return time.perf_counter() - t0
76+
77+
78+
def bench_cached_exact_hit(loops: int) -> float:
79+
_fn = find_sub_dirs
80+
_parents = _PARENTS
81+
_sub_dirs = _SUB_DIRS
82+
_fn(_parents, _sub_dirs)
83+
84+
t0 = time.perf_counter()
85+
for _ in range(loops):
86+
_fn(_parents, _sub_dirs)
87+
return time.perf_counter() - t0
88+
89+
90+
atexit.register(_TEMP_DIR.cleanup)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import atexit
6+
import os
7+
import site
8+
import tempfile
9+
import time
10+
from pathlib import Path
11+
12+
from cuda.pathfinder import find_nvidia_binary_utility, find_nvidia_header_directory, locate_static_lib
13+
from cuda.pathfinder._headers.find_nvidia_headers import locate_nvidia_header_directory
14+
from cuda.pathfinder._utils.find_sub_dirs import find_sub_dirs_cached
15+
from cuda.pathfinder._utils.platform_aware import IS_WINDOWS
16+
17+
_TEMP_DIR = tempfile.TemporaryDirectory()
18+
_ROOT = Path(_TEMP_DIR.name)
19+
_SITE_ROOTS = tuple(_ROOT / f"site-root-{index}" for index in range(3))
20+
for root in _SITE_ROOTS:
21+
root.mkdir()
22+
23+
_PACKAGE_ROOT = _SITE_ROOTS[-1] / "nvidia"
24+
_HEADER_DIR = _PACKAGE_ROOT / "cuda_runtime" / "include"
25+
_HEADER_DIR.mkdir(parents=True)
26+
(_HEADER_DIR / "cuda_runtime.h").touch()
27+
28+
_BINARY_DIR = _PACKAGE_ROOT / "cuda_nvcc" / "bin"
29+
_BINARY_DIR.mkdir(parents=True)
30+
_BINARY_NAME = "nvdisasm.exe" if IS_WINDOWS else "nvdisasm"
31+
_BINARY_PATH = _BINARY_DIR / _BINARY_NAME
32+
_BINARY_PATH.touch()
33+
if not IS_WINDOWS:
34+
_BINARY_PATH.chmod(0o755)
35+
36+
_STATIC_LIB_DIR = _PACKAGE_ROOT / "cuda_runtime" / "lib"
37+
if IS_WINDOWS:
38+
_STATIC_LIB_DIR /= "x64"
39+
_STATIC_LIB_DIR.mkdir(parents=True)
40+
_STATIC_LIB_NAME = "cudadevrt.lib" if IS_WINDOWS else "libcudadevrt.a"
41+
(_STATIC_LIB_DIR / _STATIC_LIB_NAME).touch()
42+
43+
_ORIGINAL_GETSITEPACKAGES = site.getsitepackages
44+
_ORIGINAL_ENABLE_USER_SITE = site.ENABLE_USER_SITE
45+
_ACTIVE_SITE_ROOTS: tuple[Path, ...] = _SITE_ROOTS
46+
47+
48+
def _getsitepackages() -> list[str]:
49+
return [str(root) for root in _ACTIVE_SITE_ROOTS]
50+
51+
52+
site.getsitepackages = _getsitepackages
53+
site.ENABLE_USER_SITE = False
54+
55+
56+
def _clear_discovery_caches() -> None:
57+
find_sub_dirs_cached.cache_clear()
58+
find_nvidia_binary_utility.cache_clear()
59+
locate_nvidia_header_directory.cache_clear()
60+
61+
62+
def _cleanup() -> None:
63+
site.getsitepackages = _ORIGINAL_GETSITEPACKAGES
64+
site.ENABLE_USER_SITE = _ORIGINAL_ENABLE_USER_SITE
65+
_TEMP_DIR.cleanup()
66+
67+
68+
def bench_header_cold_1_root(loops: int) -> float:
69+
global _ACTIVE_SITE_ROOTS
70+
_ACTIVE_SITE_ROOTS = _SITE_ROOTS[-1:]
71+
_fn = find_nvidia_header_directory
72+
_clear = _clear_discovery_caches
73+
74+
t0 = time.perf_counter()
75+
for _ in range(loops):
76+
_clear()
77+
_fn("cudart")
78+
return time.perf_counter() - t0
79+
80+
81+
def bench_header_cold_3_roots(loops: int) -> float:
82+
global _ACTIVE_SITE_ROOTS
83+
_ACTIVE_SITE_ROOTS = _SITE_ROOTS
84+
_fn = find_nvidia_header_directory
85+
_clear = _clear_discovery_caches
86+
87+
t0 = time.perf_counter()
88+
for _ in range(loops):
89+
_clear()
90+
_fn("cudart")
91+
return time.perf_counter() - t0
92+
93+
94+
def bench_binary_cold_1_root(loops: int) -> float:
95+
global _ACTIVE_SITE_ROOTS
96+
_ACTIVE_SITE_ROOTS = _SITE_ROOTS[-1:]
97+
_fn = find_nvidia_binary_utility
98+
_clear = _clear_discovery_caches
99+
100+
t0 = time.perf_counter()
101+
for _ in range(loops):
102+
_clear()
103+
_fn("nvdisasm")
104+
return time.perf_counter() - t0
105+
106+
107+
def bench_binary_cold_3_roots(loops: int) -> float:
108+
global _ACTIVE_SITE_ROOTS
109+
_ACTIVE_SITE_ROOTS = _SITE_ROOTS
110+
_fn = find_nvidia_binary_utility
111+
_clear = _clear_discovery_caches
112+
113+
t0 = time.perf_counter()
114+
for _ in range(loops):
115+
_clear()
116+
_fn("nvdisasm")
117+
return time.perf_counter() - t0
118+
119+
120+
def bench_static_lib_cold_3_roots(loops: int) -> float:
121+
global _ACTIVE_SITE_ROOTS
122+
_ACTIVE_SITE_ROOTS = _SITE_ROOTS
123+
_fn = locate_static_lib
124+
_clear = find_sub_dirs_cached.cache_clear
125+
126+
t0 = time.perf_counter()
127+
for _ in range(loops):
128+
_clear()
129+
_fn("cudadevrt")
130+
return time.perf_counter() - t0
131+
132+
133+
_clear_discovery_caches()
134+
assert find_nvidia_header_directory("cudart") == os.path.normpath(os.path.abspath(_HEADER_DIR))
135+
assert find_nvidia_binary_utility("nvdisasm") == os.path.abspath(_BINARY_PATH)
136+
assert locate_static_lib("cudadevrt").abs_path == os.path.join(os.path.abspath(_STATIC_LIB_DIR), _STATIC_LIB_NAME)
137+
_clear_discovery_caches()
138+
139+
atexit.register(_cleanup)

0 commit comments

Comments
 (0)