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_ESTIMATE by default; with BART_USE_FFTW_WISDOM=1 it plans with FFTW_MEASURE and saves the wisdom to files, which later calls read.
  • MRIReco.jl always plans with FFTW_MEASURE and keeps the wisdom only in memory, so every new Julia session measures again.
  • Ristretto chooses per reconstruction: FFTW_MEASURE when the transforms the solve will run repay the measuring, else FFTW_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/):

toolboximport (s)first solve (s)warm solve (s)fresh process, load and solve (s)
Ristretto2.9913.0 / 9.810.055 / 0.00818.1 / 14.9
MRIReco1.636.83 / 11.20.074 / 0.02018.7 / 17.5
MIRT1.94— / 3.68— / 0.038— / 7.10
SigPy2.180.38 / 0.0270.13 / 0.0242.60 / 2.20
MRpro3.100.18 / 0.0160.15 / 0.0103.25 / 3.07
BART0.0460.25 / 0.170.25 / 0.170.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.

casemethodRistrettoBARTBART (MEASURE)SigPyMRIRecoMIRTMRpro
Shepp–Logan 2D 1 coil CartesianAnisotropic 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 CartesianAnisotropic 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 CartesianL1-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 CartesianTGV (NRMSE≤0.02509)342 ms (0.023)1.9 s (0.018)2.1 s (0.018)————
Shepp–Logan 2D 1 coil CartesianTotal 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 CartesianTotal 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 CartesianAnisotropic 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 CartesianAnisotropic 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 CartesianL1-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 CartesianTGV (NRMSE≤0.0168)416 ms (0.015)2.3 s (0.014)2.3 s (0.014)————
Shepp–Logan 2D 8 coils CartesianTotal 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 CartesianTotal 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 radialTotal 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 CartesianTotal 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 CartesianTotal 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 CartesianGlobal 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 CartesianL1-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 CartesianLocally 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 CartesianTemporal 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 CartesianTemporal 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 radialGlobal 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.

casemethodRistrettoBARTBART (MEASURE)SigPyMRIRecoMRpro
Shepp–Logan 2D 1 coil CartesianL1-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 CartesianL1-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 radialL1-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 CartesianL1-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 CartesianL1-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.

casemethod1 thread2 threads4 threads8 threads16 threadsspeed-up
Shepp–Logan 2D 1 coil CartesianL1-Wavelet (20 it)40 ms40 ms40 ms44 ms40 ms1.0×
Shepp–Logan 2D 8 coils CartesianL1-Wavelet (20 it)69 ms62 ms61 ms57 ms55 ms1.2×
Shepp–Logan 2D 8 coils radialL1-Wavelet (20 it)947 ms467 ms295 ms207 ms209 ms4.5×
Shepp–Logan 3D 8 coils CartesianL1-Wavelet (20 it)17.8 s12.2 s8.2 s5.7 s——
Shepp–Logan multi-slice 8 coils CartesianL1-Wavelet (20 it)833 ms426 ms248 ms180 ms91 ms9.1×

GPU

The matched-effort rows on an NVIDIA GPU (CUDA), copies to and from the device included.

casemethodRistrettoBARTSigPyMRIRecoMRpro
real 2D 1 coil CartesianL1-Wavelet (20 it)17 ms (0.130)48 ms (0.163)167 ms (0.131)37 ms (0.210)—
real 2D multichannel CartesianL1-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 radialL1-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 CartesianL1-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 CartesianL1-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 CartesianL1-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 CartesianL1-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 radialL1-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 CartesianL1-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 CartesianL1-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):

toolkitversion
Ristretto0.1.0, 0.1.0 (64be4c00)
BARTv1.0.00
SigPy0.1.27
MRIReco0.9.2
MIRT0.18.3
MRpro0.260420

and behind the GPU table:

toolkitversion
Ristretto0.1.0, 0.1.0 (e240e882)
BARTv1.0.00
SigPy0.1.27
MRIReco0.9.2
MRpro0.260420