Benchmarks
The comparison harness in benchmark/comparison/ reconstructs the same cases with Ristretto, BART, SigPy, MRIReco.jl, MIRT.jl and MRpro, from the same prepared data, on the same node (see its README). Every table below is a snapshot: the Hardware and Versions sections say when it was measured and which release of each toolbox it measured.
Method. Each toolbox solves the same problem: the same encoding model, the same regularizer, with λ calibrated per toolbox so that all of them reach the same accuracy, since the toolboxes scale the data and the regularizer differently. The fair number is then time to accuracy: how long each toolbox needs to bring the NRMSE against the ground truth below a common target (the accuracy race). Fixed-iteration timings (matched effort) are listed too, but an iteration costs and achieves different things in different solvers. Times are the fastest of three runs after a warm-up, each planning its FFTs from scratch, from data on the host to an image on the host.
FFT planning. FFTW chooses how to compute a transform when it is planned. FFTW_ESTIMATE guesses from the sizes and plans in well under a millisecond; FFTW_MEASURE times candidate algorithms on the machine, which costs about 0.1–0.2 s per 2D transform and 1–1.5 s per 3D one and gives a plan that runs faster. What FFTW learns by measuring ("wisdom") can be saved and reused by a later plan of the same transform. The toolboxes differ here:
- BART plans with
FFTW_ESTIMATEby default; withBART_USE_FFTW_WISDOM=1it plans withFFTW_MEASUREand saves the wisdom to files, which later calls read. - MRIReco.jl always plans with
FFTW_MEASUREand keeps the wisdom only in memory, so every new Julia session measures again. - Ristretto chooses per reconstruction:
FFTW_MEASUREwhen the transforms the solve will run repay the measuring, elseFFTW_ESTIMATE(fft_planning = :auto, the default), and saves the wisdom to disk for later sessions. - SigPy and MRpro do not use FFTW: NumPy's and PyTorch's FFTs plan without a measuring step. On the GPU every toolbox uses cuFFT.
The benchmarks charge every run its planning: no wisdom survives from one run to the next, neither in memory nor on disk. MRIReco therefore pays its measuring in every run, about 0.3 s per solve on the 128² Shepp–Logan case, most of its time there. BART is timed both ways: the BART (MEASURE) column measures in every run, the BART column estimates. Measuring costs BART about 0.3 s per 2D solve, several times its estimating time there, and about breaks even on the 3D and cine cases, where the faster plans run long enough to repay it.
Start-up and warm-up
The tables below favour the toolboxes that are slow to start. They time warm solves, while a user also pays to start the toolbox: a BART call is a new process that starts in milliseconds, a Python toolbox has to be imported, and a Julia toolbox also compiles each solver the first time it is called. Measured on one case (Shepp–Logan 2D, 8 coils, Cartesian; ℓ₁-wavelet / CG-SENSE), 8 threads, in fresh processes (benchmark/startup/):
| toolbox | import (s) | first solve (s) | warm solve (s) | fresh process, load and solve (s) |
|---|---|---|---|---|
| Ristretto | 2.99 | 13.0 / 9.81 | 0.055 / 0.008 | 18.1 / 14.9 |
| MRIReco | 1.63 | 6.83 / 11.2 | 0.074 / 0.020 | 18.7 / 17.5 |
| MIRT | 1.94 | — / 3.68 | — / 0.038 | — / 7.10 |
| SigPy | 2.18 | 0.38 / 0.027 | 0.13 / 0.024 | 2.60 / 2.20 |
| MRpro | 3.10 | 0.18 / 0.016 | 0.15 / 0.010 | 3.25 / 3.07 |
| BART | 0.046 | 0.25 / 0.17 | 0.25 / 0.17 | 0.25 / 0.17 |
The Julia numbers assume precompiled packages; precompiling Ristretto itself once takes about two minutes more. For BART every call is a fresh process, so its first and warm solves are the same and include the process start and file I/O that the tables below leave out. The first solve in a Julia session is therefore two to three orders of magnitude slower than the warm one, which matters for a script that reconstructs one image and hardly at all for a session that reconstructs many.
Time to accuracy
Target NRMSE against the ground truth in the method column; time per toolbox, the fastest in bold, the NRMSE reached in brackets. 8 threads, OpenBLAS. BART's TGV is not quite the same penalty: since BART 1.0 it weights the two terms by (1, √3) and halves the symmetrised gradient, so its λ is calibrated separately and only the target is shared.
| case | method | Ristretto | BART | BART (MEASURE) | SigPy | MRIReco | MIRT | MRpro |
|---|---|---|---|---|---|---|---|---|
| Shepp–Logan 2D 1 coil Cartesian | Anisotropic TV (NRMSE≤0.05859) | 22 ms (0.055) | 77 ms (0.012) | 405 ms (0.012) | 144 ms (0.053) | 452 ms (0.051) | — | — |
| Shepp–Logan 2D 1 coil Cartesian | Anisotropic TV (PDHG) (NRMSE≤0.05859) | 89 ms (0.022) | 153 ms (0.046) | 489 ms (0.046) | 674 ms (0.051) | — | — | 1.0 s (0.052) |
| Shepp–Logan 2D 1 coil Cartesian | L1-Wavelet (NRMSE≤0.1993) | 41 ms (0.186) | 73 ms (0.192) | 406 ms (0.192) | 67 ms (0.191) | 365 ms (0.188) | — | 92 ms (0.185) |
| Shepp–Logan 2D 1 coil Cartesian | TGV (NRMSE≤0.02509) | 342 ms (0.023) | 1.9 s (0.018) | 2.1 s (0.018) | — | — | — | — |
| Shepp–Logan 2D 1 coil Cartesian | Total Variation (NRMSE≤0.09241) | 21 ms (0.056) | 174 ms (0.034) | 509 ms (0.034) | 93 ms (0.055) | — | — | — |
| Shepp–Logan 2D 1 coil Cartesian | Total Variation (PDHG) (NRMSE≤0.09241) | 109 ms (0.055) | 477 ms (0.039) | 845 ms (0.039) | 491 ms (0.084) | — | — | 199 ms (0.083) |
| Shepp–Logan 2D 8 coils Cartesian | Anisotropic TV (NRMSE≤0.04141) | 59 ms (0.041) | 630 ms (0.006) | 802 ms (0.006) | 573 ms (0.035) | 713 ms (0.008) | — | — |
| Shepp–Logan 2D 8 coils Cartesian | Anisotropic TV (PDHG) (NRMSE≤0.04141) | 619 ms (0.022) | 885 ms (0.029) | 977 ms (0.029) | 1.9 s (0.038) | — | — | 402 ms (0.026) |
| Shepp–Logan 2D 8 coils Cartesian | L1-Wavelet (NRMSE≤0.2653) | 82 ms (0.230) | 285 ms (0.156) | 431 ms (0.156) | 275 ms (0.233) | 411 ms (0.231) | — | 211 ms (0.241) |
| Shepp–Logan 2D 8 coils Cartesian | TGV (NRMSE≤0.0168) | 416 ms (0.015) | 2.3 s (0.014) | 2.3 s (0.014) | — | — | — | — |
| Shepp–Logan 2D 8 coils Cartesian | Total Variation (NRMSE≤0.08046) | 46 ms (0.072) | 279 ms (0.069) | 567 ms (0.069) | 561 ms (0.041) | — | — | — |
| Shepp–Logan 2D 8 coils Cartesian | Total Variation (PDHG) (NRMSE≤0.08046) | 238 ms (0.036) | 907 ms (0.074) | 968 ms (0.074) | 1.8 s (0.073) | — | — | 393 ms (0.068) |
| Shepp–Logan 2D 8 coils radial | Total Variation (NRMSE≤0.1138) | 245 ms (0.096) | 345 ms (0.103) | 1.0 s (0.081) | 4.7 s (0.101) | — | — | — |
| Shepp–Logan 3D 8 coils Cartesian | Total Variation (NRMSE≤0.1516) | 6.6 s (0.119) | 11.6 s (0.105) | 11.8 s (0.105) | 98.5 s (0.100) | — | — | — |
| Shepp–Logan multi-slice 8 coils Cartesian | Total Variation (NRMSE≤0.1033) | 273 ms (0.078) | 7.1 s (0.025) | 9.2 s (0.025) | 2.6 s (0.073) | — | — | — |
| torso cine 8 coils Cartesian | Global Low-Rank (NRMSE≤0.07498) | 1.1 s (0.073) | 13.2 s (0.068) | 12.6 s (0.068) | 14.4 s (0.064) | 3.1 s (0.073) | 37.6 s (0.069) | 20.9 s (0.065) |
| torso cine 8 coils Cartesian | L1-Wavelet (NRMSE≤0.1824) | 463 ms (0.174) | 1.7 s (0.181) | 1.7 s (0.181) | 6.3 s (0.176) | 1.6 s (0.177) | — | 2.8 s (0.175) |
| torso cine 8 coils Cartesian | Locally Low-Rank (NRMSE≤0.06021) | 2.9 s (0.057) | 11.6 s (0.056) | 9.9 s (0.056) | — | 5.5 s (0.052) | — | — |
| torso cine 8 coils Cartesian | Temporal TV (NRMSE≤0.01589) | 1.4 s (0.011) | 6.2 s (0.007) | 5.7 s (0.007) | 14.6 s (0.010) | — | — | — |
| torso cine 8 coils Cartesian | Temporal TV (PDHG) (NRMSE≤0.01589) | 9.2 s (0.008) | 36.4 s (0.009) | 34.6 s (0.009) | 93.6 s (0.007) | — | — | 18.4 s (0.015) |
| torso cine 8 coils radial | Global Low-Rank (NRMSE≤0.1248) | 2.4 s (0.117) | 2.9 s (0.123) | 3.0 s (0.123) | 59.4 s (0.114) | 3.8 s (0.114) | 167.5 s (0.114) | 12.9 s (0.118) |
Matched effort
ℓ₁-wavelet regularized SENSE, 20 iterations of each toolbox's default solver for the problem.
| case | method | Ristretto | BART | BART (MEASURE) | SigPy | MRIReco | MRpro |
|---|---|---|---|---|---|---|---|
| Shepp–Logan 2D 1 coil Cartesian | L1-Wavelet (20 it) | 44 ms (0.186) | 167 ms (0.218) | 489 ms (0.218) | 67 ms (0.191) | 363 ms (0.188) | 91 ms (0.185) |
| Shepp–Logan 2D 8 coils Cartesian | L1-Wavelet (20 it) | 57 ms (0.277) | 196 ms (0.328) | 400 ms (0.328) | 247 ms (0.272) | 384 ms (0.267) | 163 ms (0.270) |
| Shepp–Logan 2D 8 coils radial | L1-Wavelet (20 it) | 207 ms (0.278) | 366 ms (0.279) | 1.0 s (0.279) | 4.1 s (0.280) | 678 ms (0.291) | 405 ms (0.286) |
| Shepp–Logan 3D 8 coils Cartesian | L1-Wavelet (20 it) | 5.7 s (0.217) | 8.3 s (0.174) | 9.1 s (0.174) | 53.3 s (0.191) | 22.0 s (0.198) | 21.1 s (0.196) |
| Shepp–Logan multi-slice 8 coils Cartesian | L1-Wavelet (20 it) | 180 ms (0.283) | 2.3 s (0.338) | 5.2 s (0.338) | 2.7 s (0.264) | 4.7 s (0.263) | 1.9 s (0.259) |
Thread scaling
Ristretto's time for the matched-effort ℓ₁-wavelet reconstruction, from 1 to 16 threads. Single-coil 2D problems are too small to gain from threads and run serially by design.
| case | method | 1 thread | 2 threads | 4 threads | 8 threads | 16 threads | speed-up |
|---|---|---|---|---|---|---|---|
| Shepp–Logan 2D 1 coil Cartesian | L1-Wavelet (20 it) | 40 ms | 40 ms | 40 ms | 44 ms | 40 ms | 1.0× |
| Shepp–Logan 2D 8 coils Cartesian | L1-Wavelet (20 it) | 69 ms | 62 ms | 61 ms | 57 ms | 55 ms | 1.2× |
| Shepp–Logan 2D 8 coils radial | L1-Wavelet (20 it) | 947 ms | 467 ms | 295 ms | 207 ms | 209 ms | 4.5× |
| Shepp–Logan 3D 8 coils Cartesian | L1-Wavelet (20 it) | 17.8 s | 12.2 s | 8.2 s | 5.7 s | — | — |
| Shepp–Logan multi-slice 8 coils Cartesian | L1-Wavelet (20 it) | 833 ms | 426 ms | 248 ms | 180 ms | 91 ms | 9.1× |
GPU
The matched-effort rows on an NVIDIA GPU (CUDA), copies to and from the device included.
| case | method | Ristretto | BART | SigPy | MRIReco | MRpro |
|---|---|---|---|---|---|---|
| real 2D 1 coil Cartesian | L1-Wavelet (20 it) | 17 ms (0.130) | 48 ms (0.163) | 167 ms (0.131) | 37 ms (0.210) | — |
| real 2D multichannel Cartesian | L1-Wavelet (20 it) | 20 ms (0.225) | 38 ms (0.223) | 144 ms (0.224) | 216 ms (0.258) | 94 ms (0.237) |
| real 2D multichannel radial | L1-Wavelet (20 it) | 40 ms (0.290) | 710 ms (0.284) | 220 ms (0.301) | 285 ms (0.335) | 451 ms (0.297) |
| real 3D multichannel Cartesian | L1-Wavelet (20 it) | 238 ms (0.585) | 509 ms (0.304) | 4.2 s (0.396) | 12.3 s (0.488) | 869 ms (0.477) |
| real multi-slice multichannel Cartesian | L1-Wavelet (20 it) | 90 ms (0.231) | 351 ms (0.226) | 1.7 s (0.234) | 2.4 s (0.260) | 1.1 s (0.247) |
| Shepp–Logan 2D 1 coil Cartesian | L1-Wavelet (20 it) | 15 ms (0.186) | 31 ms (0.218) | 103 ms (0.191) | 55 ms (0.188) | 78 ms (0.185) |
| Shepp–Logan 2D 8 coils Cartesian | L1-Wavelet (20 it) | 20 ms (0.277) | 31 ms (0.328) | 100 ms (0.272) | 154 ms (0.266) | 95 ms (0.270) |
| Shepp–Logan 2D 8 coils radial | L1-Wavelet (20 it) | 19 ms (0.279) | 162 ms (0.279) | 173 ms (0.280) | 102 ms (0.291) | 380 ms (0.286) |
| Shepp–Logan 3D 8 coils Cartesian | L1-Wavelet (20 it) | 312 ms (0.217) | 506 ms (0.174) | 3.6 s (0.191) | 12.3 s (0.198) | 998 ms (0.196) |
| Shepp–Logan multi-slice 8 coils Cartesian | L1-Wavelet (20 it) | 78 ms (0.287) | 274 ms (0.338) | 1.2 s (0.264) | 1.8 s (0.263) | 1.2 s (0.259) |
Hardware
- openblas, 8 thread(s): AMD EPYC 7763 64-Core Processor; Julia 1.13.1; BLAS libopenblas64_.so; measured 2026-10-10 to 2026-10-12.
- cuda, 1 thread(s): AMD EPYC 7763 64-Core Processor, GPU NVIDIA A100-SXM4-40GB; Julia 1.13.0, 1.13.1; BLAS libopenblas64_.so; measured 2026-10-02 to 2026-10-12.
Versions
The toolbox versions behind the CPU tables (8 threads, OpenBLAS):
| toolkit | version |
|---|---|
| Ristretto | 0.1.0, 0.1.0 (64be4c00) |
| BART | v1.0.00 |
| SigPy | 0.1.27 |
| MRIReco | 0.9.2 |
| MIRT | 0.18.3 |
| MRpro | 0.260420 |
and behind the GPU table:
| toolkit | version |
|---|---|
| Ristretto | 0.1.0, 0.1.0 (e240e882) |
| BART | v1.0.00 |
| SigPy | 0.1.27 |
| MRIReco | 0.9.2 |
| MRpro | 0.260420 |