Performance evidence#

SplineOps treats performance as reproducible evidence, not as an adjective. The project is especially strong when a workload needs explicit spline semantics, projection antialiasing, N-D execution, or reusable resize plans. It does not claim to beat specialized image libraries on every ordinary 2-D resize.

Evidence summary#

What the current evidence establishes#

Capability

Evidence

Interpretation

Native/reference agreement

Contract and property tests across shapes, dtypes, and concurrency.

The accelerated resize path follows the maintained numerical reference.

Native acceleration

Reproducible native-versus-reference benchmarks.

This measures internal acceleration, not competitive superiority.

Repeated geometry

ResizePlan and query-plan benchmarks.

Reuse can avoid meaningful setup work in matching workloads.

Cross-library behavior

Timings plus numerical and semantic differences.

Libraries with different grids, boundaries, or filters are not treated as equivalent algorithms.

Independent competitive accuracy

Not yet established across representative N-D applications.

No universal best-quality claim is made.

Numerical difference from SplineOps is not an accuracy metric unless an independent ground truth is available. All dated values below are development snapshots rather than portable guarantees.

TensorSpline baseline#

scripts/benchmark_tensorspline.py separates coefficient construction from evaluation and records both runtime and tracemalloc peak allocations. On 2026-07-15, the standard float64 profile produced the following medians on Linux 6.17, Python 3.12.3, and NumPy 2.5.1:

Standard TensorSpline profile#

Case

Construction

Evaluation

Evaluation peak

1-D cubic grid

132.7 ms

35.7 ms

8.50 MiB

2-D cubic grid

38.4 ms

15.9 ms

2.54 MiB

2-D cubic, 200,000 points

39.6 ms

276.9 ms

22.03 MiB

3-D linear grid

5.8 ms

37.9 ms

6.01 MiB

3-D cubic grid

22.0 ms

11.1 ms

1.73 MiB

These are a development-machine baseline, not portable promises. The useful result is the bounded growth of point-query temporaries and the lower working set for separable tensor grids. Point evaluation is tiled at 65,536 samples; unbatched grids contract one axis at a time instead of materializing the full coordinate-product support arrays.

An explicit point-count sweep excludes the already-allocated input coordinates from tracing and subtracts the required output buffer. On the same machine, the standard cubic 2-D sweep measured:

TensorSpline point-query memory scaling#

Query points

Traced peak

Temporary overhead

10,000

3.21 MiB

3.13 MiB

100,000

21.27 MiB

20.50 MiB

1,000,000

28.14 MiB

20.51 MiB

The flat overhead between 100,000 and 1,000,000 points is evidence that the working set is governed by the tile size rather than the full query. The result array itself must still scale with the requested output.

Repeated coordinates#

TensorSpline.query_plan trades capped retained memory for repeated-query speed. It stores coordinate geometry independently of sample values and can therefore serve compatible splines representing changing frames. For 200,000 random cubic 2-D points and seven changing-spline evaluations, the ordinary path took 187.1 ms per call and the planned path 30.0 ms: a 6.23x speedup. Plan construction broke even after an estimated 1.62 calls and retained 24.41 MiB. This is a strong workload-specific capability, not a reason to plan one-shot queries.

Reproduce it with:

python scripts/benchmark_tensorspline.py --profile standard \
  --output-json tensorspline.json --output-csv tensorspline.csv
python scripts/benchmark_tensorspline_memory_scaling.py \
  --output-json tensorspline-memory.json
python scripts/benchmark_tensorspline_query_plan.py \
  --output-json tensorspline-plan.json

Affine comparison#

scripts/benchmark_affine.py compares exactly matched pull-back geometry, whole-sample mirror boundaries, prefiltering, output shape, and spline degree against scipy.ndimage.affine_transform. The standard 2026-07-15 run found maximum differences near 1e-13, but SciPy was materially faster:

Equivalent affine rotation profile#

Case

SplineOps one-shot

Cached AffinePlan

SciPy

SciPy vs. one-shot

2-D linear

170.0 ms

18.0 ms

8.5 ms

20.02x

2-D cubic

569.4 ms

192.4 ms

21.3 ms

26.69x

3-D linear

123.8 ms

21.1 ms

12.2 ms

10.17x

3-D cubic

456.7 ms

98.3 ms

54.3 ms

8.41x

Affine’s current achievements are exact spline semantics and bounded coordinate memory. Direct NumPy support contraction materially improves the cached plan, especially in 3-D, but it still does not beat SciPy’s specialized kernels on these cases. Maximum absolute differences were between 7e-14 and 6e-13. CuPy deliberately retains the previously tested contraction until dedicated GPU evidence is available.

Reproduce the profile with:

python scripts/benchmark_affine.py --profile standard \
  --output-json affine.json --output-csv affine.csv

Affine phase profile#

scripts/profile_affine_phases.py instruments prefilter and coefficient evaluation inside complete cached-plan calls and reports construction separately. On the current standard run, evaluation accounted for 63%–91% of the measured in-call phase time across 2-D/3-D linear/cubic rows. Cached-plan construction ranged from 57 ms to 326 ms and is therefore justified only by reuse. The profile motivated sequential support-axis contraction for NumPy 3-D, where the gathered cubic support block is largest. NumPy 1-D/2-D and CuPy retain the prior direct contraction because the evidence did not justify broadening the change.

python scripts/profile_affine_phases.py --profile standard \
  --output-json affine-phases.json --output-csv affine-phases.csv

Differentials vectorization#

The spline differential implementation performs coefficient conversion and derivative stencils across complete array axes. Against the retained scalar row/column oracle, the standard 512x640 float64 gradient-magnitude benchmark measured 210.0 ms cold versus 12.582 s, a 59.92x speedup. Reusing the Differentials object’s cached workspace reduced a repeated map to 4.0 ms. A DifferentialPlan request for gradient, packed Hessian, and Laplacian together took 348.1 ms with a 22.62 MiB traced peak. Gradient-only execution took 211.9 ms; Laplacian-only execution took 214.3 ms and used 66.8% of the full request’s traced peak because it omitted gradient and mixed-Hessian outputs. Maximum absolute difference from the scalar oracle was 1.556e-7.

python scripts/benchmark_differentials.py --profile standard \
  --output-json differentials.json --output-csv differentials.csv

Reusable research-module plans#

SmoothingSplinePlan retains a real-FFT half-spectrum response for changing arrays with fixed shape, regularization, and order. DenoisingPlan retains the sparse factorization determined by fixed sample locations and ADMM penalty while allowing observations and regularization strength to change.

scripts/benchmark_workflows.py measures complete operations, including prefiltering and axis orchestration. After the batch-execution pass, the standard five-repeat medians on the same development machine were:

Consolidated workflow profile#

Workflow

Consolidated path

Explicit reference

Speedup

One coefficient field, two affine geometries

44.7 ms

70.0 ms

1.57x

Affine with explicit batch/channel axes

78.2 ms

151.2 ms

1.93x

Smoothing with explicit batch/channel axes

22.3 ms

21.0 ms

0.94x

Multi-output differentials with explicit axes

111.1 ms

159.2 ms

1.43x

Warm-start denoising lambda path

1.148 s

1.155 s

1.01x

Haar explicit-axis analysis/synthesis

30.9 ms

31.0 ms

1.00x

All compared outputs agreed exactly except the independently converged ADMM paths, whose maximum difference was 5.6e-9. The mixed result is useful: coefficient sharing gains 1.57x, direct batched affine contraction gains 1.93x, and batched multi-output differentials gain 1.43x. Adaptive Haar execution removes the former large-plane regression and is neutral here. Batched smoothing is 6% slower for these four large planes, so its axis API remains a convenience rather than a speed claim. Nearby denoising lambda paths can need fewer iterations, but the actual diagnostics—not the API name—decide whether that helps.

The subsequent standard CI matrix confirmed why these are local measurements. The explicit-axis affine ratio was 0.70x on Linux, 0.87x on macOS, and 0.76x on Windows; the Windows differential ratio was 1.01x. Every corresponding output still passed its numerical gate. The stored policy therefore treats these batch APIs as allocation/orchestration contracts, keeps a broad 0.5x complete-workflow regression floor, and does not require a portable speed win.

python scripts/benchmark_workflows.py --profile standard \
  --output-json workflows.json --output-csv workflows.csv
python scripts/check_benchmark_thresholds.py \
  --policy benchmarks/consolidation-thresholds.json \
  --artifacts-dir .

Explicit batch memory scaling#

scripts/benchmark_batch_scaling.py sweeps batch counts 1, 2, 4, and 8 at 64x80, 128x160, and 256x320. On the same standard profile, every batched path agreed exactly with explicit plane dispatch. The largest normalized traced peak was 1.08: peak memory therefore remained linear or sublinear relative to the one-plane baseline within measurement noise. At 256x320 and batch eight, traced peaks were 22.63 MiB for affine, 30.09 MiB for Laplacian-only differentials, and 12.00 MiB for the adaptive Haar round trip.

These are Python allocation traces, not whole-process RSS measurements. The affine rows report cached geometry separately, and the required output buffer is separated from temporary overhead in the JSON/CSV artifacts.

python scripts/benchmark_batch_scaling.py --profile standard \
  --output-json batch-scaling.json --output-csv batch-scaling.csv

Downstream stability-soak workflows#

scripts/benchmark_downstream_workflows.py measures complete pipelines, not isolated kernels. Persisted registration includes atomic archive I/O, compatibility validation, and three concurrent affine geometries. Buffered volume features apply a 3-D affine transform and write gradient and Laplacian outputs into caller-owned arrays. Both standard rows agreed exactly with their explicit scalar references. On this development machine registration fan-out measured 288.2 ms versus 505.6 ms (1.75x), while buffered volume features measured 156.1 ms versus 232.8 ms (1.49x).

The ratios are workload evidence, not isolated-kernel claims: registration’s timed path deliberately includes filesystem persistence and thread-pool execution. See Stability-soak workflows for the contracts being exercised.

python scripts/benchmark_downstream_workflows.py --profile standard \
  --output-json downstream-workflows.json \
  --output-csv downstream-workflows.csv

Multiscale vectorization#

scripts/benchmark_multiscale.py compares current whole-axis execution with the retained row/column oracle on standard float64 images. Pyramid reduction measured 479.3 ms versus 3.668 s (7.65x), and one-scale Haar analysis measured 29.6 ms versus 68.8 ms (2.33x). These ratios measure Python dispatch and array execution on one development machine; reconstruction tolerances and supported shape contracts are unchanged.

python scripts/benchmark_multiscale.py --profile standard \
  --output-json multiscale.json --output-csv multiscale.csv

Resize v2 scheduler profile#

The native resize scheduler was re-profiled after the v2 direct cross-Gram rewrite rather than carrying forward pre-v2 tuning assumptions. A proposed serial path for small 3-D work was rejected: one thread was typically three to five times slower in the focused sweep. The retained policy keeps parallel execution but caps automatic participation at eight workers for 3-D workloads of at most one million elements. An explicit LSRESIZE_NUM_THREADS value continues to override automatic selection.

In an 11-repeat focused profile, automatic/default versus explicit-eight medians were 0.62/0.64 ms for linear downsampling, 3.51/3.12 ms for cubic downsampling, 1.51/1.67 ms for anisotropic cubic work, 2.43/2.21 ms for linear antialiasing, and 2.60/2.32 ms for cubic antialiasing. The policy tracks the measured eight-worker operating point broadly; individual rows can still move in either direction, and it is not a claim that eight threads is ideal on every machine.

The broader library comparison remains contextual. SciPy, OpenCV, PyTorch, and SplineOps differ in coordinates, boundaries, antialiasing, and output-size rules. SplineOps therefore reports both numerical differences and timings rather than presenting those libraries as one universal leaderboard. See Project status and evidence for the smoke-comparison command.

Historical optimization reports#

The repository contains scripts/resize_optimization_notes.md, scripts/resize_optimization_progress.md, and scripts/resize_benchmark_report_20260616.md. They are useful engineering history, but parts describe the finite-difference projection implementation replaced by the 2.0 direct cross-Gram path. They must not be quoted as current v2 benchmark results. New resize reports should be generated from the versioned scripts and should state wins, losses, numerical differences, hardware, thread settings, and semantic mismatches.