import argparse
import resource
import sys
import time
import numpy as np
import dlpack_test_module as dtm
def _bench(label, fn, iters):
for _ in range(5):
fn()
start = time.perf_counter()
for _ in range(iters):
fn()
end = time.perf_counter()
total = end - start
ns_per = (total / iters) * 1e9
print(f"{label}: {ns_per:,.0f} ns/op")
def _time_ns(fn, iters):
for _ in range(5):
fn()
start = time.perf_counter()
for _ in range(iters):
fn()
return ((time.perf_counter() - start) / iters) * 1e9
def run_compare(sizes, iters):
try:
import torch
have_torch = True
except ImportError:
have_torch = False
print(f"{'elements':>12} {'pyo3-dlpack (zc)':>18} {'numpy.copy':>14} {'torch.clone':>14}")
for size in sizes:
arr = np.arange(size, dtype=np.float32)
def zerocopy():
dtm.import_tensor(arr)
def npcopy():
_ = arr.copy()
zc = _time_ns(zerocopy, iters)
nc = _time_ns(npcopy, iters)
if have_torch:
t = torch.from_numpy(arr)
tc = _time_ns(lambda: t.clone(), iters)
tc_s = f"{tc:>12,.0f}ns"
else:
tc_s = f"{'n/a':>14}"
print(f"{size:>12,} {zc:>15,.0f}ns {nc:>11,.0f}ns {tc_s}")
def _peak_rss_mb():
ru = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
return ru / (1024 * 1024) if sys.platform == "darwin" else ru / 1024
def run_memory(size):
arr = np.arange(size, dtype=np.float32)
base = _peak_rss_mb()
dtm.import_tensor(arr) after_zc = _peak_rss_mb()
dup = arr.copy() after_copy = _peak_rss_mb()
mb = arr.nbytes / (1024 * 1024)
print(f"buffer: {mb:.1f} MiB")
print(f" baseline peak RSS: {base:8.1f} MiB")
print(f" after zero-copy import: {after_zc:8.1f} MiB (+{after_zc-base:.1f})")
print(f" after numpy.copy(): {after_copy:8.1f} MiB (+{after_copy-after_zc:.1f})")
del dup
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--size", type=int, default=1_000_000, help="Number of f32 elements")
parser.add_argument("--iters", type=int, default=200, help="Iterations per benchmark")
parser.add_argument("--compare", action="store_true", help="latency vs size sweep")
parser.add_argument("--memory", action="store_true", help="peak-RSS comparison")
args = parser.parse_args()
if args.compare:
run_compare([1_000_000, 10_000_000, 100_000_000], args.iters)
return
if args.memory:
run_memory(50_000_000)
return
size = args.size
iters = args.iters
arr = np.arange(size, dtype=np.float32)
bytes_total = arr.nbytes
mb = bytes_total / (1024 * 1024)
print(f"Array size: {size} elements ({mb:.2f} MiB)")
def bench_import():
dtm.import_tensor(arr)
def bench_export():
cap = dtm.export_large_cpu_tensor(size)
class _CapsuleWrapper:
def __init__(self, capsule):
self._capsule = capsule
def __dlpack__(self):
return self._capsule
np.from_dlpack(_CapsuleWrapper(cap))
def bench_copy():
_ = arr.copy()
_bench("import_numpy_to_rust", bench_import, iters)
_bench("export_rust_to_numpy", bench_export, iters)
_bench("numpy_copy_baseline", bench_copy, iters)
if __name__ == "__main__":
main()