I'm observing a strange behaviour concerning the scipy.linalg.eig_banded
eigensolver.
I am generating banded matrices of size N=p*f that have a specific structure. The matrices are symmetric tri-block-diagonal with p blocks of size fxf on the main diagonal and p-1 identity matrices of size f*f on the off diagonals.
Example with p=3 and f=3:
[2 2 2 1 0 0 0 0 0]
[2 2 2 0 1 0 0 0 0]
[2 2 2 0 0 1 0 0 0]
[1 0 0 3 3 3 1 0 0]
[0 1 0 3 3 3 0 1 0]
[0 0 1 3 3 3 0 0 1]
[0 0 0 1 0 0 4 4 4]
[0 0 0 0 1 0 4 4 4]
[0 0 0 0 0 1 4 4 4]
Usually these matrices are of size p = 100, f=30, N=p*f=3000 but can easily grow much larger.
Given the structure of these matrices I was hoping that the banded eigensolver in scipy was going to be much faster than the dense eigensolver, however it seems like this is not the case.
I am benchmarking the solvers with the following code:
# Set dimension of problem
f = 50
p = 80
a = 1
print(f"p={p}, f={f}, size={f*p, f*p}")
print(f"Matrix containing random numbers in {(-a, a)}")
A = generate_matrix(p, f, -a, a)
# Benchmark standard eigensolver
start = time()
D, Q = linalg.eigh(A)
end = time()
# Test correctness
D = np.diag(D)
print(f"Time for dense solver {end - start}")
print(f"||AQ - QD|| = {np.linalg.norm(A@Q - Q@D)}")
# Convert A to banded format
A_banded = banded_format(A, upper = f)
# Benchmark banded eigensolver
start = time()
D, Q = linalg.eig_banded(A_banded)
end = time()
# Test correctness
D = np.diag(D)
print(f"Time for banded solver {end - start}")
print(f"||AQ - QD|| = {np.linalg.norm(A@Q - Q@D)}")
The results I get indicate that the banded eigensolver is much slower than the dense one:
p=80, f=50, size=(4000, 4000)
Matrix containing random numbers in (-1, 1)
Time for dense solver 13.475645780563354
||AQ - QD|| = 3.1334336527852233e-12
Time for banded solver 24.427151203155518
||AQ - QD|| = 1.589349711533356e-11
I have already tried storing the matrix in lower diagonal format and passing the overwrite_a_band=True
option, but the performance remains the same.
Numpy configuration:
blas_mkl_info:
NOT AVAILABLE
blis_info:
NOT AVAILABLE
openblas_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
blas_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
lapack_mkl_info:
NOT AVAILABLE
openblas_lapack_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
lapack_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
Scipy configuration:
lapack_mkl_info:
NOT AVAILABLE
openblas_lapack_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
lapack_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
blas_mkl_info:
NOT AVAILABLE
blis_info:
NOT AVAILABLE
openblas_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
blas_opt_info:
libraries = ['openblas', 'openblas']
library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
language = c
define_macros = [('HAVE_CBLAS', None)]
runtime_library_dirs = ['/cluster/apps/gcc-8.2.0/openblas-0.2.20-5gatj7a35vypgjekzf3ibbtz54tlbk3m/lib']
I also tried running the same benchmark on a different cluster using MKL as a backend instead of OpenBLAS and I observed very similar results. Also setting the number of threads with OMP_NUM_THREADS
and/or MKL_NUM_THREADS
has a very small effect on performance.
Does anyone have any ideas on why this is happening?
Thanks