From f712474c6833ae653cd08cbc9ece57cbb70258a6 Mon Sep 17 00:00:00 2001 From: Faruk Alpay <32020561+farukalpay@users.noreply.github.com> Date: Sun, 24 Aug 2025 13:35:47 +0200 Subject: [PATCH] vectorize rosenbrock hessian and add benchmarks --- README.md | 13 +++++++++++ benchmarks/profiling_notes.md | 13 +++++++++++ benchmarks/rosenbrock_hess_baseline.json | 1 + benchmarks/test_rosenbrock_hess.py | 28 ++++++++++++++++++++++++ src/psd/functions.py | 15 ++++++++----- 5 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 benchmarks/profiling_notes.md create mode 100644 benchmarks/rosenbrock_hess_baseline.json create mode 100644 benchmarks/test_rosenbrock_hess.py diff --git a/README.md b/README.md index 29950c8..6c7fbe3 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,19 @@ python experiments.py The command writes CSV summaries to `results/` and training curves to `data/`. +## Performance + +Profiling identified `rosenbrock_hess` as a hot path when computing the +Rosenbrock Hessian. Vectorising the computation removed explicit +Python loops and yielded the following improvements (dimension 1000): + +| Version | Mean time (ms) | Peak memory (MB) | +|---------|----------------|-----------------| +| Before | 3.52 | 8.00 | +| After | 0.67 | 8.04 | + +Benchmarking is automated via `pytest-benchmark` with a ±5% budget gate. + ### Training with the PyTorch Optimiser ```python diff --git a/benchmarks/profiling_notes.md b/benchmarks/profiling_notes.md new file mode 100644 index 0000000..14461d1 --- /dev/null +++ b/benchmarks/profiling_notes.md @@ -0,0 +1,13 @@ +# Profiling Notes + +## Setup +- Function: `rosenbrock_hess` with input dimension 1000. +- Profiling tools: `cProfile` for CPU time, `tracemalloc` for memory. + +## Results +| Version | Mean time (ms) | Peak memory (MB) | +|---------|----------------|-----------------| +| Before | 3.52 | 8.00 | +| After | 0.67 | 8.04 | + +The profiler highlighted repeated Python loops in the original implementation as the primary cost. Replacing these loops with NumPy vectorized operations yielded about a 5x speedup while keeping memory usage roughly constant. diff --git a/benchmarks/rosenbrock_hess_baseline.json b/benchmarks/rosenbrock_hess_baseline.json new file mode 100644 index 0000000..2b7c619 --- /dev/null +++ b/benchmarks/rosenbrock_hess_baseline.json @@ -0,0 +1 @@ +{"mean": 0.0006741321449242711, "peak": 8043508} \ No newline at end of file diff --git a/benchmarks/test_rosenbrock_hess.py b/benchmarks/test_rosenbrock_hess.py new file mode 100644 index 0000000..c5bc3eb --- /dev/null +++ b/benchmarks/test_rosenbrock_hess.py @@ -0,0 +1,28 @@ +import json +from pathlib import Path + +import numpy as np +import tracemalloc + +from psd.functions import rosenbrock_hess + +BASELINE = Path(__file__).with_name("rosenbrock_hess_baseline.json") +TOL = 0.05 + + +def test_rosenbrock_hess_speed(benchmark): + x = np.random.rand(1000) + benchmark(rosenbrock_hess, x) + mean = benchmark.stats["mean"] + tracemalloc.start() + rosenbrock_hess(x) + _, peak = tracemalloc.get_traced_memory() + tracemalloc.stop() + data = {"mean": mean, "peak": peak} + if BASELINE.exists(): + baseline = json.loads(BASELINE.read_text()) + assert mean <= baseline["mean"] * (1 + TOL) + assert mean >= baseline["mean"] * (1 - TOL) + assert peak <= baseline["peak"] * (1 + TOL) + else: + BASELINE.write_text(json.dumps(data)) diff --git a/src/psd/functions.py b/src/psd/functions.py index 47dafcb..8129153 100644 --- a/src/psd/functions.py +++ b/src/psd/functions.py @@ -240,11 +240,16 @@ def rosenbrock_hess(x: Array) -> Array: x = np.asarray(x) d = len(x) hess = np.zeros((d, d)) - for i in range(d - 1): - hess[i, i] += 1200.0 * x[i] ** 2 - 400.0 * x[i + 1] + 2.0 - hess[i, i + 1] += -400.0 * x[i] - hess[i + 1, i] += -400.0 * x[i] - hess[i + 1, i + 1] += 200.0 + if d > 1: + idx = np.arange(d - 1) + diag = 1200.0 * x[idx] ** 2 - 400.0 * x[idx + 1] + 2.0 + hess[idx, idx] = diag + hess[idx + 1, idx + 1] += 200.0 + off = -400.0 * x[idx] + hess[idx, idx + 1] = off + hess[idx + 1, idx] = off + else: + hess[0, 0] = 200.0 return hess