Installing NumPy with MKL support
NumPy has to be compiled against mkl, so you can't just switch BLAS/LAPACK libraries at runtime. If you are happy to use conda, you can install NumPy with MKL-support from conda-forge or anaconda. The other answer already provides good details if you want to go that route.
If you want to stick to pip, you can install them from, e.g., https://github.com/urob/numpy-mkl/ using the following command:
pip install numpy --extra-index-url https://urob.github.io/numpy-mkl
MKL vs BLAS/LAPACK
To compare performance between the different BLAS/LAPACK implementations you can create two virtual environments.
Here I'm slightly modifying the original benchmark script from the question. The modified script runs the benchmark for multiple problem sizes and replaces time with timeit.Timer (which disables garbage collection during the timing to make them more comparible).
import inspect
import sys
from timeit import Timer
import numpy as np
fmt = "{name:<17}: {time:.4f}s ({loops:6d} loops)"
class Benchmark:
def run(self):
for size, loops in zip(self.size, self.loops):
best = self.run_single(size, loops)
name = self.__class__.__name__ + f"({size}x{size})"
print(fmt.format(name=name, time=best, loops=loops))
def run_single(self, size, loops, repeat=5):
self.setup(size)
self.time_it() # Run once to warm up JIT if needed
t = Timer(lambda: self.time_it())
return min(t.repeat(repeat=repeat, number=loops))
class Matmul(Benchmark):
size = (10, 100, 1000)
loops = (100_000, 10_000, 100)
def setup(self, k):
rng = np.random.default_rng(1)
self.a = rng.random((k, k))
self.b = rng.random((k, k))
def time_it(self):
_ = np.dot(self.a, self.b)
class Eigen(Benchmark):
size = (10, 100, 1000)
loops = (10_000, 100, 5)
def setup(self, k):
rng = np.random.default_rng(1)
self.a = rng.random((k, k))
def time_it(self):
_ = np.linalg.eig(self.a)
def get_benchmarks():
cls = inspect.getmembers(sys.modules[__name__], inspect.isclass)
return [c for _, c in cls if issubclass(c, Benchmark) and c is not Benchmark]
if __name__ == "__main__":
for b in get_benchmarks():
b().run()
Benchmark results
# MKL
python -m venv mkl && source mkl/bin/activate
pip install numpy --extra-index-url https://urob.github.io/numpy-mkl
python benchmark.py
Eigen(10x10) : 0.3221s ( 10000 loops)
Eigen(100x100) : 0.3209s ( 100 loops)
Eigen(1000x1000) : 4.0278s ( 5 loops)
Matmul(10x10) : 0.1164s (100000 loops)
Matmul(100x100) : 0.1922s ( 10000 loops)
Matmul(1000x1000): 0.9143s ( 100 loops)
# BLAS/LAPACK
python -m venv nomkl && source nomkl/bin/activate
pip install numpy
python benchmark.py
Eigen(10x10) : 0.4455s ( 10000 loops)
Eigen(100x100) : 0.8334s ( 100 loops)
Eigen(1000x1000) : 3.9544s ( 5 loops)
Matmul(10x10) : 0.0999s (100000 loops)
Matmul(100x100) : 0.3058s ( 10000 loops)
Matmul(1000x1000): 1.0825s ( 100 loops)
Interestingly, the biggest performance differences can be seen for mid-sized problems, where MKL is significantly faster than OpenBLAS/LAPACK (more than twice as fast for eig). For small and large problems, the differences are smaller (and in fact OpenBLAS is even faster than MKL for matrix multiplication of very small matrices and when computing eigenvalues for very large matrices).