Skip to content
Speed

Speed

Against SciPy's vectorised calls on the same data: the method, the results, and where it is slower and why.

The comparison is with SciPy 1.18.1 / NumPy 2.5.4 (native arm64 wheels, the build a user of this machine runs), vectorised calls, on the same data.

Method

  • go test -run TestPerfReport -perf.report=docs/perf-table.md starts bench/scipy_server.py through uv and drives it over a pipe. For every case and every round it times, back to back, the SciPy call, a single-goroutine Go loop calling the scalar method (elementwise cases), and the call a Go user makes (stats.Map(x, d.CDF), which splits the array over GOMAXPROCS goroutines; or the function itself).
  • Points are generated identically on both sides (lo + (hi-lo)·frac(0.618…·i)), normal samples by the same seeded generator (go-ndarray reproduces NumPy’s).
  • Rounds are interleaved case by case so that the load of the shared host hits both sides alike; the table reports medians of 7 rounds. SciPy’s time includes allocating its result, as Go’s does.
  • The host is shared (16 cores; load average in the table header, about 30 during the run). Absolute times are inflated for both sides; the ratios are the robust numbers, and the single-goroutine column is the fair core-for-core comparison.

Results

perf-table.md (generated). Summary, Go (all cores) against SciPy (one core, as SciPy runs):

caseSciPy (ms)Go (ms)ratio
norm.pdf, 10^615.812.61.26x faster
norm.cdf, 10^629.817.91.66x faster
norm.ppf, 10^627.143.91.6x slower
beta.pdf, 10^694.277.91.21x faster
gamma.cdf, 10^62752431.13x faster
beta.cdf, 10^62985952x slower
beta.ppf, 10^517311496.6x slower
t.cdf, 10^62758423x slower
t.ppf, 10^550109622x slower
norm.rvs / gamma.rvs, 10^69.6 / 46.611.2 / 34.20.86x / 1.36x
ttest_ind, 10^6 + 10^66.87.70.88x
ttest_1samp, 10^63.13.20.99x
pearsonr, 10^65.99716x slower
spearmanr, 10^68613682.3x faster
describe, 10^6184603.1x faster
percentile (5 q), 10^640.727.21.5x faster
gaussian_kde evaluate, 10^4 × 10^49161138.1x faster

Where it is slower, and why

  • Single core, the scalar distribution functions are 2–15x slower than SciPy’s C/C++ ufuncs (the one-goroutine column). The accuracy costs: the densities carry their exponent in double-double and exponentiate with a nearly correctly rounded exponential; the parallel Map recovers the difference for the cheap functions on a multi-core machine.
  • t and beta cdf/ppf go through go-numerics/special’s incomplete beta function and its inverse, which are 10–15x slower than Boost’s (their own measurement, go-numerics/special docs/perf.md); t.ppf additionally pays two inverse evaluations to get both z and 1-z without cancellation. A scalar StudentT.PPF call is about 75 µs: irrelevant for a confidence interval, slow for 10^5 quantiles.
  • norm.ppf is special.Ndtri (correctly rounded but for rare cases), slower than Cephes’ rational approximation (which is off by up to 2.8 ulp).
  • Pearson computes its sums of products in double-double (exact correlation for nearly collinear data), where SciPy uses a BLAS dot product.
  • t-tests are at parity: both sides are a couple of passes of pairwise sums over the data.