Performance of Arbitrary-Precision Numerical Libraries#
(last update: September 17, 2026)
Summary#
For computations that need linear algebra and FFTs at 100–1000 decimal digits (quantum-mechanical calculations in my case), I measured the speed of the major arbitrary-precision libraries and systems under identical conditions. This is a sequel to my 2020 Qiita article (in Japanese). The scope is now (A) scalar arithmetic, (B) diagonalization of nonsymmetric and Hermitian matrices, and (C) complex FFTs, and the article also covers the effect of parallelization and estimates of the actual computing resources (time and memory) required.
The conclusions first:
Sequential (single-threaded) arbitrary-precision diagonalization falls within a factor of about 2 regardless of language or library. Speed is set by the cost of arbitrary-precision scalar operations; the quality of the implementation contributes only a factor of about 2.
The way out is parallelization, and among the candidates tested only Advanpix parallelizes the eigenvalue routines themselves. On 64 physical cores, nonsymmetric diagonalization at \(d=1000\) digits and matrix size \(N=300\) runs 19× faster than on one thread, and 20–60× faster than the sequential group. SMT (128 threads), however, is counterproductive; the thread count must be pinned to the number of physical cores.
MPLAPACK's OpenMP BLAS (Rgemm) scales well, 56× on 64 cores, but more than 90% of the eigenvalue routines (Rgeev/Cheev) is spent in QR iteration and eigenvector computation (level 1 and 2 operations), so the parallel BLAS yields only a 1.1× gain.
For scalar arithmetic, arb's transcendental functions are 4–6× faster than MPFR's, and the transcendental functions of Boost.Multiprecision's cpp_bin_float are unusable. If fixed precision suffices, MultiFloats (Float64x2/x4) is faster than dd/qd and __float128.
For FFTs of power-of-two length, a simple OpenMP implementation is fast enough (\(2^{20}\) points at 1000 digits in 1 second on 128 threads). For composite lengths Advanpix is the only practical option; FLINT becomes 50× slower than at \(2^{20}\).
The resources needed by Advanpix (64 threads) can be estimated as time \(t \approx 6\times10^{-7}\,(d/1000)\,N^3\) seconds (conservative) and memory \(M \approx 0.7\ \mathrm{GB} + N^2\,(700 + 4.7\,d)\) bytes, where \(d\) is the number of digits. Diagonalizing \(N=10000\) at 1000 digits needs 525 GB of memory; memory becomes the limiting factor before time does.
About the writing of this article#
This article was produced with Anthropic's Claude (Fable 5.1, reasoning level: medium). The evaluation plan was drafted by Claude, based on the previous evaluation, with the goal of being as fair as possible. The benchmark code for each language, the aggregation and fitting scripts, the interpretation of the results, and the draft of this text were produced interactively; running the measurements, checking the results, and configuring the environment were all done by a human. Responsibility for the content lies with the author (in princicple. I honestly has hard to keep up with one :-< ).
I have also come to appreciate how mature the numerical libraries for arbitrary precision and interval arithmetic have become, and I thank the maintainers who develop them. This article is only an evaluation, as of September 17, 2026, of the part of my own research that is the computational bottleneck; with generative AI, the code of each library will presumably improve further.
1. Environment and design of the measurements#
Machine#
Item |
Content |
|---|---|
CPU |
AMD Ryzen Threadripper PRO 3995WX (64 cores / 128 threads, Zen 2, 1 NUMA node) |
Memory |
DDR4, 8 channels, 512 GB (ECC) |
OS / compiler |
Debian sid, gcc 16.2, |
Libraries |
GMP 6.3, MPFR 4.2.2, FLINT 3.6.0 (with arb merged in), MPLAPACK 3.0.0, Eigen 3.4 + mpreal, Boost.Multiprecision, QD 2.3 |
Language systems |
Julia 1.13 (GenericSchur, GenericLinearAlgebra, MultiFloats 3.3, Arblib 1.8, GenericFFT), Python 3.14 (mpmath 1.3 + gmpy2, python-flint), MATLAB R2026a + Advanpix MCT 5.4.8, Mathematica 15.0 |
The CPU frequency governor was pinned to performance, and no other jobs ran during the measurements. OpenMP threads were pinned to cores with OMP_PROC_BIND=true OMP_PLACES=cores. Each point is the median of 3 trials (1 trial for large \(N\)). The variation between trials was 0.1–1%, smaller than the markers in the figures.
Tasks#
(A) Scalar arithmetic: add / mul / div / sqrt / exp / log / sin / dot (inner product) applied repeatedly to arrays \(x\), \(y\) of length \(10^5\); the time per operation is recorded in ns. gemm is the time in seconds of one real dense matrix product with \(N=100\).
(B) Diagonalization: the nonsymmetric matrix is the same Circus matrix as last time (\(A_{i,i}=1,\ A_{i+1,i}=-1,\ A_{i,i+k}=k+1\ (k=1,\dots,4)\)); the Hermitian matrix is a complex matrix whose upper triangle is generated from a common random sequence. \(N = 10\)–\(1000\). The call measured computes both eigenvalues and right eigenvectors (the equivalent of MATLAB's [V,D]=eig(A)). As checks, \(|\sum_k \lambda_k - \mathrm{tr}\,A|\) and the residual \(\max_k \|Av_k-\lambda_k v_k\|_\infty/(\|A\|_\infty\|v_k\|_\infty)\) were verified for every system (Appendix A).
(C) FFT: time of one forward transform of complex length \(n = 2^{12}, 2^{16}, 2^{20}, 10^6\) (composite \(= 2^6\cdot5^6\)). Checked by the round-trip error.
Precision: 100 digits (334 bits) and 1000 digits (3323 bits). Fixed-precision types (double-double = 32 digits, quad-double = 64 digits, binary128 = 34 digits) are shown separately at their effective number of digits.
Random numbers: every language uses the same xorshift64 sequence, so the same matrices and arrays are processed everywhere.
Two pitfalls#
Two things I noticed only after starting the measurements are worth stating first; both affect the interpretation of the results.
The first is the mantissa length of the input. When a random number generated in double precision is converted to an arbitrary-precision type, the mantissa has 53 bits and the rest is zero. arb operates on the actual length of the mantissa, so with such input a 1000-digit multiplication finishes in 10 ns, and MPFR also skips trailing zero limbs in multiplication and appears about 6× faster than it is. Here the inputs are sqrt(u), which fills the mantissa to the target precision. Be wary of benchmarks that use "values converted from double precision".
The second is that the Circus matrix is already upper Hessenberg (lower bandwidth 1). A general eigenvalue computation has three stages, Hessenberg reduction → QR iteration → eigenvector computation, and for the Circus matrix the first stage is trivial. So the diagonalization benchmarks, both last time and this time, measure the speed of "QR iteration and eigenvector computation" and do not reflect the performance of the Hessenberg reduction, which contains BLAS3 and is the easiest part to parallelize. The effect of this is examined separately with dense matrices in Section 5.
All code and raw measurements (CSV) are published (Appendix C).
2. Scalar arithmetic#

Time per operation at 100 digits (ns). add/mul/div/sqrt/exp/log/sin are per array element; gemm is converted to the time in seconds of one \(N=100\) matrix product.
System |
add |
mul |
div |
sqrt |
exp |
log |
sin |
dot |
gemm N=100 [s] |
|---|---|---|---|---|---|---|---|---|---|
MPFR C API (preallocated) |
28 |
51 |
113 |
202 |
2360 |
4340 |
2460 |
74 |
0.081 |
Boost.Multiprecision mpfr_float |
40 |
63 |
131 |
215 |
2400 |
4330 |
2470 |
87 |
0.087 |
mpreal (C++ wrapper) |
88 |
111 |
175 |
264 |
2450 |
4410 |
2510 |
124 |
0.124 |
Julia BigFloat |
114 |
155 |
175 |
273 |
2520 |
4430 |
2760 |
186 |
0.127 |
FLINT arb (C API) |
54 |
57 |
128 |
226 |
678 |
564 |
769 |
46 |
— |
Arblib.jl |
263 |
255 |
313 |
481 |
887 |
725 |
797 |
515 |
0.288 |
Boost cpp_bin_float |
56 |
91 |
462 |
822 |
30700 |
39800 |
12500 |
156 |
0.156 |
Mathematica |
901 |
387 |
925 |
634 |
1010 |
939 |
1520 |
287 |
0.270 |
Advanpix (64-thread array operations) |
79 |
67 |
81 |
45 |
79 |
126 |
100 |
179 |
0.003 |
The baseline is the raw MPFR C API: add 28 ns, mul 51 ns. The thinness of the wrapper shows directly: Boost's mpfr_float costs +40%, mpreal 3×, Julia's BigFloat 4×. The latter two create and destroy objects (malloc) on every operation; in Julia every BigFloat operation allocates on the heap.
Transcendental functions look different. arb's exp/log/sin (680 / 560 / 770 ns) are 4–6× faster than MPFR's (2.4 / 4.3 / 2.5 µs). arb has its own algorithms (piecewise polynomial approximation and the bit-burst method), which is a reason to choose arb for computations that call transcendental functions heavily at high precision. Boost's cpp_bin_float is more than 10× slower than MPFR for transcendental functions (exp takes 4 ms at 1000 digits) and should not be used at 100 digits or more.
Advanpix executes array operations on 64 threads, so this table shows "64-thread array operations divided by the number of elements". It dominates for heavy operations — exp at 79 ns, gemm at 3 ms — but is slower than MPFR for light operations such as add (79 ns, 1 µs at 1000 digits), because the thread start-up and memory-bandwidth overheads are not hidden by light operations. The picture is "heavy operations: Advanpix; large loops of light operations: not suited".
At 1000 digits, multiplication converges to about 950 ns in every system. They all share GMP's multiplication kernel, so wrapper differences vanish in this regime. A notable point is Mathematica: its transcendental functions are among the slowest at 100 digits (1 µs) but 3× faster than MPFR at 1000 digits (exp 18 µs vs 60 µs); its high-precision algorithms are well done.
Fixed-precision types (effective 32–64 digits) form a separate table.
Type |
Digits |
add |
mul |
div |
sqrt |
exp |
log |
sin |
|---|---|---|---|---|---|---|---|---|
MultiFloats Float64x2 |
32 |
1.4 |
0.9 |
1.3 |
1.7 |
122 |
37 |
2670* |
QD dd_real |
32 |
1.2 |
2.9 |
7.9 |
10.5 |
232 |
261 |
232 |
DoubleFloats Double64 |
32 |
7.6 |
7.4 |
7.8 |
3.4 |
71 |
62 |
150 |
__float128 |
34 |
20.5 |
18.9 |
32 |
406 |
646 |
652 |
533 |
MultiFloats Float64x4 |
64 |
7.0 |
7.1 |
23 |
42 |
903 |
1300 |
3590* |
QD qd_real |
64 |
20 |
37 |
202 |
602 |
2330 |
7320 |
2120 |
(* sin in MultiFloats falls back to BigFloat)
Float64x2 matches or beats dd_real with add 1.4 ns and mul 0.9 ns, and Float64x4 is 3–5× faster than qd_real. MultiFloats benefits from being a Julia type that vectorizes with SIMD. __float128 takes 20 ns, 15× slower than Float64x2 at almost the same precision (34 vs 32 digits), so on x86, which has no hardware support for it, double-double is the better deal.
3. Diagonalization — sequential performance#

First, the single-threaded comparison. Time in seconds for the nonsymmetric (Circus) matrix at \(N=300\).
System |
100 digits |
1000 digits |
Arithmetic |
|---|---|---|---|
Advanpix |
34.9 |
653 |
real |
MPLAPACK Rgeev (GMP mpf) |
51.4 |
1310 |
real |
MPLAPACK Rgeev (MPFR) |
71.0 |
971 |
real |
Mathematica Eigensystem |
89.1 |
700 |
real |
Arblib.jl approx_eig_qr |
93.8 |
1850 |
complex |
Julia GenericSchur |
103 |
794 |
real |
Eigen EigenSolver + mpreal |
112 |
790 |
real |
FLINT acb_mat_approx_eig_qr |
113 |
2170 |
complex |
python-flint |
(N=200: 27.3) |
— |
complex |
mpmath |
(N=200: 678) |
— |
real |
The systems fall into two groups. Those that solve with real Hessenberg QR (Advanpix, MPLAPACK, Mathematica, GenericSchur, Eigen) sit together at 650–970 s for 1000 digits, and those that solve in complex arithmetic (FLINT, Arblib) are 2–3× slower. The eigenvalues of the Circus matrix are complex but the matrix is real, so implementations that go through the real Schur form avoid complex multiplications (4 real multiplications each). The remaining differences are implementation quality and lie within a factor of 1.5.
Mathematica, which looked slow in the previous article, is in the fastest class as long as the arbitrary-precision Eigensystem is used sequentially (700 s at 1000 digits, \(N=300\), faster than GenericSchur and Eigen). The earlier impression came from comparing it with the parallelized Advanpix. mpmath is an order of magnitude slower (678 s at 100 digits, \(N=200\), versus 20–35 s for the others); from Python, python-flint (as fast as FLINT in C) is the one to use.
The differences widen with \(N\). Going from \(N=300\) to \(1000\) at 100 digits, MPLAPACK goes from 71 to 1470 s (20.7×, \(\approx N^{2.5}\)), while GenericSchur goes from 103 to 3610 s and Arblib from 94 to 3240 s (35×, \(\approx N^{3.0}\)); at \(N=1000\) MPLAPACK is 2.5× faster than GenericSchur. This is presumably the regime in which the LAPACK-derived blocking pays off in cache efficiency. At 1000 digits the cost of a single operation dominates and this difference disappears (at \(N=500\): GenericSchur 3580 s, MPLAPACK 3980 s).
MPLAPACK's backend is best chosen by precision. At 100 digits GMP's mpf, which is lighter because it does not do correct rounding, is 1.3–1.5× faster than MPFR; at 1000 digits MPFR's multiplication optimizations win and it is 1.4× faster.

Hermitian matrix (\(N=300\), 1 thread, seconds).
System |
100 digits |
1000 digits |
Solver |
|---|---|---|---|
Advanpix |
18.2 |
246 |
dedicated (auto-detected) |
MPLAPACK Cheev (GMP) |
55.2 |
951 |
dedicated |
MPLAPACK Cheev (MPFR) |
82.2 |
749 |
dedicated |
Arblib.jl |
89.6 |
1230 |
general |
FLINT |
89.9 |
1420 |
general |
GenericSchur eigen(Hermitian) |
93.4 |
821 |
dedicated |
GenericLinearAlgebra |
103 |
841 |
dedicated |
Eigen SelfAdjointEigenSolver |
109 |
736 |
dedicated |
Mathematica |
347 |
2110 |
dedicated (auto-detected) |
Surprisingly, there is almost no difference between the dedicated Hermitian solvers (tridiagonalization + QR) and FLINT's general solver. In double precision a dedicated solver is several times faster than a general one, but in arbitrary precision the cost of scalar operations dominates and the difference in operation count (1/3–1/4 of the general solver) hardly shows in the time. Mathematica is last for Hermitian matrices. The same Mathematica solves the nonsymmetric Circus matrix in 700 s (1000 digits, \(N=300\)) but takes 2110 s, 3× longer, for the Hermitian matrix. In every other system the Hermitian case is as fast as or faster than the nonsymmetric one, so only Mathematica is reversed. HermitianMatrixQ returns True, so the dedicated path should be taken, which means that path is slower than the general one. Advanpix is the only system in which the dedicated routine clearly pays off: even on one thread its Hermitian solve is 2.7× faster than its nonsymmetric one.
Certified eigenvalues (FLINT acb_mat_eig_multiple, which proves enclosures of the eigenvalues with interval arithmetic) work at 100 digits with an interval radius of \(10^{-56}\) at \(N=100\), but the radius grows to \(10^{-8}\) at \(N=200\) and the certification fails at \(N=300\). At 1000 digits it succeeds up to \(N=300\) but takes 3.3 hours (the approximate solution takes 36 minutes). The condition number of the eigenvectors of the Circus matrix deteriorates rapidly with \(N\), so certified computation is limited to \(N \le 100\) or to cases with a large precision margin.
Among fixed-precision types, MultiFloats' Float64x2 takes 2.8 s at \(N=300\) and Float64x4 24 s (35× faster than 100-digit BigFloat), and 101 s / 815 s even at \(N=1000\). If 32–64 digits suffice, there is no reason to use an arbitrary-precision type.
4. Diagonalization — parallelization#

Advanpix time (seconds, \(N=300\)) as the thread count is varied from 1 to 128.
Threads |
1 |
2 |
4 |
8 |
16 |
32 |
64 |
128 |
|---|---|---|---|---|---|---|---|---|
Nonsymmetric, 100 digits |
34.9 |
18.9 |
10.5 |
8.5 |
5.8 |
5.2 |
5.8 |
15.9 |
Nonsymmetric, 1000 digits |
653 |
339 |
180 |
103 |
62 |
42 |
35 |
189 |
Hermitian, 1000 digits |
246 |
131 |
73 |
44 |
29 |
22 |
22 |
38 |
At 1000 digits the scaling is close to ideal up to the 64 physical cores, 18.7×; at 100 digits it stalls at 6.7× with 32 threads. With fewer digits the parallel granularity is too fine and the thread start-up overhead becomes relatively large. With 128 SMT (Simultaneous Multi-Threading, Hyper-Threading) threads the run becomes more than 5× slower; even at \(N=10\), 5 ms becomes 500 ms. When using Advanpix, pin maxNumCompThreads to the number of physical cores. Parallel efficiency also improves with \(N\): at 1000 digits the time from \(N=300\) to \(1000\) grows only 21× (equivalent to \(N^{2.5}\)).
No other system gets faster with more threads. MPLAPACK's OpenMP build (--enable-optimization) gives 1.04–1.08× for Rgeev/Cheev even at 8 threads and degrades at 128. FLINT's approx_eig_qr is independent of the thread count, and Julia's GenericSchur/Arblib are not parallelized. The "overwhelming" advantage of Advanpix seen in the previous article is parallelization: a difference of 1.2–3× on one thread becomes 20–60× on 64 threads.
5. Where MPLAPACK's parallelization takes effect#
MPLAPACK is a port of the reference LAPACK to arbitrary-precision types, with OpenMP versions of some BLAS routines (Rgemm and others). To find out why this did not help the eigenvalue computation, I measured Rgemm, Rgehrd (Hessenberg reduction), and Rgeev (general eigenproblem) individually on a dense random matrix (sqrt input) and gave the same matrix to Advanpix.
Routine |
Digits |
N |
1 thread |
8 |
64 |
Speedup at 64 |
|---|---|---|---|---|---|---|
MPLAPACK Rgemm |
100 |
1000 |
100 s |
14.4 |
2.30 |
44 |
Advanpix gemm |
100 |
1000 |
76.7 |
9.9 |
2.01 |
38 |
MPLAPACK Rgemm |
1000 |
1000 |
977 |
125 |
17.4 |
56 |
Advanpix gemm |
1000 |
1000 |
1260 |
166 |
27.1 |
46 |
MPLAPACK Rgehrd |
100 |
1000 |
199 |
72 |
59 |
3.4 |
MPLAPACK Rgehrd |
1000 |
1000 |
1671 |
587 |
461 |
3.6 |
MPLAPACK Rgeev (dense) |
100 |
300 |
57.1 |
49.1 |
49.5 |
1.16 |
MPLAPACK Rgeev (dense) |
1000 |
300 |
831 |
759 |
757 |
1.10 |
Advanpix eig (dense) |
100 |
300 |
44.7 |
10.0 |
6.6 |
6.8 |
Advanpix eig (dense) |
1000 |
300 |
756 |
117 |
39.5 |
19 |
Rgemm alone scales well, 44–56× on 64 cores, as the author claims; at 1000 digits it is 1.3–1.5× faster than Advanpix's matrix product. The Hessenberg reduction Rgehrd benefits from Rgemm in its blocked part, but the level 2 operations (gemv inside Rlahr2) remain, so it saturates at 3.5×. The double-precision dgehrd has the same structure.
The issue is the breakdown of Rgeev: for the dense matrix at \(N=300\) and 1000 digits, Rgehrd takes 46 s while the whole Rgeev takes 831 s. The Hessenberg reduction is only 5–10% of the total; the remaining 90% or more goes to QR iteration (Rhseqr) and eigenvector computation (Rtrevc). In the reference LAPACK these two are written with level 1 and 2 operations and never call BLAS3, so however fast the parallel BLAS is, Rgeev can gain at most 1.1×. The 1.04× on the Circus matrix was because the Hessenberg reduction was trivial there, and the dense case does not change the essence.
Double precision is no different: numpy's and Mathematica's double-precision eig appear to use several cores because the gemm in the underlying OpenBLAS / MKL is parallel. LAPACK itself is not parallelized. In double precision the parts that call gemm (zgehrd, zlaqr5, ztrevc3) make up a fair fraction and give a speedup of a few times, but in arbitrary precision the gemm calls are too fine-grained and even that effect vanishes.
Advanpix, by contrast, goes from 756 s on one thread (about the same as MPLAPACK's 831 s) to 39.5 s on 64 threads, 19×, on the same dense matrix. Since the single-thread times coincide, the whole difference is parallelization, and Advanpix presumably parallelizes the QR iteration or the eigenvector computation itself. The back-substitution for eigenvectors is independent for each eigenvalue and easy to parallelize with OpenMP, and the sweeps of the multishift QR can be split by column blocks. For MPLAPACK, parallelizing Rtrevc and Rlaqr5 looks like the most effective next step after the parallel BLAS.
In summary, MPLAPACK is "a faithful arbitrary-precision port of the reference LAPACK plus a well-scaling parallel BLAS3", and within that design it behaves as expected. Benefiting from parallelism in eigenvalue problems requires work outside the BLAS, and that applies not only to MPLAPACK but to every implementation with the reference LAPACK structure.
6. FFT#

Time of one complex FFT (seconds), at the best thread count of each system; single-thread values in parentheses.
System |
\(2^{20}\), 100 digits |
\(2^{20}\), 1000 digits |
\(10^{6}\), 1000 digits |
|---|---|---|---|
Home-made OpenMP radix-2 (MPFR) |
0.156 (5.1) |
0.96 (41) |
not supported |
FLINT acb_dft |
0.416 |
2.3 (50) |
115 |
Advanpix fft |
1.32 |
5.2 (59) |
4.8 |
Arblib.jl |
5.71 |
43.5 |
102 |
Mathematica Fourier |
17.5 |
45 |
159 |
GenericFFT.jl |
13.3 |
57 |
927 |
For power-of-two lengths the single-thread times are all close — home-made radix-2 41 s, Arblib 43 s, Mathematica 45 s, FLINT 50 s, Advanpix 59 s — and here too the cost of scalar operations dominates. Threads make the difference: the home-made implementation takes 0.96 s on 128 threads, FLINT 2.3 s, Advanpix 5.2 s. The butterflies within each stage of an FFT are independent, so even plain OpenMP wins as long as the threads actually work.
For the composite length \(10^6\) (\(= 2^6\cdot5^6\)) the picture reverses. Advanpix takes 4.8 s, on par with \(2^{20}\), but FLINT takes 115 s (50× its \(2^{20}\) time), Arblib 102 s, Mathematica 159 s, and GenericFFT 927 s; the non-power-of-two path of FLINT's acb_dft is effectively unusable. Arblib.jl's value equals FLINT's single-thread value because it is not threaded when called from Julia.
The practical guideline is simply "use power-of-two lengths; if that is impossible, use Advanpix".
7. Estimating the resources needed by Advanpix#
To estimate the time and memory needed to actually run eigenvalue computations, I ran nonsymmetric diagonalizations for \(N=100\)–\(2000\) with Advanpix (64 threads), one point per process, recording the peak resident memory of the process (VmHWM in /proc/self/status).

N |
100 digits, time |
100 digits, peak RSS |
1000 digits, time |
1000 digits, peak RSS |
|---|---|---|---|---|
300 |
5.7 s |
0.8 GB |
35 s |
1.1 GB |
500 |
17 s |
1.0 GB |
120 s |
2.0 GB |
1000 |
79 s |
1.9 GB |
12.4 min |
6.1 GB |
1500 |
3.8 min |
3.3 GB |
36 min |
12.8 GB |
2000 |
8.1 min |
5.4 GB |
80 min |
22 GB |
A power-law fit of the time gives \(N^{2.28}\) at 100 digits and \(N^{2.56}\) at 1000 digits, less than 3, but this is because the parallel efficiency improves with \(N\); once it saturates, the exponent returns to 3. For extrapolation I therefore also use a conservative formula with \(N^3\) anchored at the largest measured point.
The linearity in the number of digits \(d\) holds only within the range checked, 100–1000 digits (below it the fixed overhead dominates; above it the superlinear cost of multiplication kicks in).
Memory follows \(N^2\) closely, and separating the bytes per element from the 100- and 1000-digit coefficients gives
The fixed 700 B is the object overhead of MPFR / Advanpix, and the 4.7 B per digit is about 11× the theoretical mantissa size (0.415 B per digit), i.e. eig keeps working storage equivalent to roughly 10 input matrices.
From these, estimates for any \(N\) follow.
\(N\) |
100 digits, time (fit / \(N^3\)) |
100 digits, memory |
1000 digits, time (fit / \(N^3\)) |
1000 digits, memory |
|---|---|---|---|---|
3000 |
18 min / 27 min |
11 GB |
3.5 h / 4.5 h |
48 GB |
5000 |
58 min / 2.1 h |
29 GB |
13 h / 21 h |
132 GB |
10000 |
4.7 h / 17 h |
115 GB |
3.2 days / 7 days |
525 GB |
\(N=10000\) at 1000 digits does not fit in the memory of this machine (512 GB). That memory becomes the limit before time does is an important point when choosing a machine.
The implications for choosing a machine: (1) the number of physical cores translates directly into speed (SMT is unnecessary, and better disabled); (2) the more digits, the more cores pay off; (3) handling \(N \ge 5000\) at 1000 digits needs at least 256 GB of memory, and \(N=10000\) needs 768 GB–1 TB; (4) the scaling of the matrix product is sensitive to memory bandwidth, so an 8-channel configuration (Threadripper PRO / EPYC class) is desirable — the question is how to secure the budget.
8. Summary by use case#
Use case |
Recommendation |
Notes |
|---|---|---|
Fast diagonalization of large matrices (many-core machine available) |
Advanpix |
Pin to physical cores. \(N=1000\) at 1000 digits in 12 min |
Sequential, no license |
GenericSchur (Julia), Eigen + mpreal, MPLAPACK |
All within a factor of 2. MPLAPACK: GMP for 100 digits, MPFR for 1000. MPLAPACK has the edge for \(N \ge 1000\) |
Certified eigenvalues |
FLINT eig_multiple |
\(N \le 100\) as a guide; fails for ill-conditioned matrices |
32–64 digits suffice |
MultiFloats (Julia) |
30–40× faster than BigFloat |
Many transcendental function calls |
arb / Advanpix |
4–6× faster than MPFR |
FFT |
Home-made OpenMP or FLINT for power-of-two lengths; Advanpix for composite lengths |
Avoid FLINT for composite lengths |
From Python |
python-flint |
mpmath is an order of magnitude slower |
Avoid |
cpp_bin_float (transcendentals), __float128 (speed), GenericFFT, Mathematica's Hermitian diagonalization |
In the end, the time of arbitrary-precision linear algebra is determined by "how many scalar operations of 50 ns–1 µs each are called". Implementations differ by about 2×, and any further speedup comes only from parallelization. Among the candidates tested only Advanpix implements it; conversely, if parallel eigenvector computation were added to MPLAPACK or GenericSchur, they could compete on the same footing.
Appendix#
A. Verification values#
In every system the residual \(\max_k \|Av_k-\lambda_k v_k\|_\infty/(\|A\|_\infty\|v_k\|_\infty)\) was 1–2 digits below the target precision (\(10^{-99}\)–\(10^{-98}\) at 100 digits, \(10^{-300}\) or less at 1000 digits). MPLAPACK-GMP actually runs at 115 digits when 100 are requested, because the precision of mpf is rounded up to a multiple of 64 bits. The round-trip errors of the FFTs were likewise at the target precision.
B. Record of pitfalls#
Converting inputs from double precision shortens the mantissa and makes arb / MPFR multiplication appear faster than it is (Section 1).
The Circus matrix is upper Hessenberg and the cost of Hessenberg reduction is not included (Sections 1 and 5).
The GMP backend of MPLAPACK 3.0 ignores
mpf_set_default_prec; the precision must be set withgmpxx::set_default_mpf_precision_bits(if this goes unnoticed, the default 512 bits stays fixed and the 100- and 1000-digit runs take the same time).Advanpix becomes more than 5× slower when SMT threads are used (Section 4).
FLINT's certified eigenvalues take an explosive amount of time on ill-conditioned matrices (3.3 hours at 1000 digits, \(N=300\)).
C. Code and data#
mpbench contains the complete set of programs and results used for this article. mpbench/readme describes how to build and run them (in Japanese). mpbench/env.txt records the CPU, memory, kernel, compiler, and library versions. mpbench/results.csv and mpbench/advanpix_scaling.csv contain the measurements.