Python/Numpy中的多线程BLAS
我正在尝试在Python中实现大量的矩阵乘法。最开始,我以为NumPy会自动使用我安装的多线程BLAS库,因为我在安装时是针对这些库进行配置的。不过,当我查看top或者其他工具时,似乎代码根本没有使用多线程。
有没有什么想法,是什么问题,或者我该怎么做才能轻松利用BLAS的性能呢?
4 个回答
我之前在另一个帖子里发过这个,但我觉得在这里更合适:
更新(2014年7月30日):
我在我们的新高性能计算机(HPC)上重新进行了基准测试。 硬件和软件的配置都与原始答案中的设置不同。
我把结果放在了一个谷歌表格中(也包含了原始答案的结果)。
硬件
我们的高性能计算机有两个不同的节点,一个是使用英特尔的Sandy Bridge处理器,另一个是使用更新的Ivy Bridge处理器:
Sandy(MKL、OpenBLAS、ATLAS):
- CPU: 2 x 16 英特尔(R) 至强(R) E2560 Sandy Bridge @ 2.00GHz(16个核心)
- 内存: 64 GB
Ivy(MKL、OpenBLAS、ATLAS):
- CPU: 2 x 20 英特尔(R) 至强(R) E2680 V2 Ivy Bridge @ 2.80GHz(20个核心,开启超线程后为40个核心)
- 内存: 256 GB
软件
两个节点的软件配置是一样的。使用的是OpenBLAS而不是GotoBLAS2,并且还有一个设置为8线程的多线程ATLAS BLAS。
- 操作系统: Suse
- 英特尔编译器: ictce-5.3.0
- Numpy: 1.8.0
- OpenBLAS: 0.2.6
- ATLAS:: 3.8.4
点积基准测试
基准测试代码与下面的相同。不过对于新机器,我还对5000和8000的矩阵大小进行了基准测试。
下面的表格包含了原始答案中的基准测试结果(重命名为:MKL --> Nehalem MKL,Netlib Blas --> Nehalem Netlib BLAS,等等)
单线程性能:
多线程性能(8线程):
线程与矩阵大小(Ivy Bridge MKL):
基准测试套件
单线程性能:
多线程(8线程)性能:
结论
新的基准测试结果与原始答案中的结果相似。OpenBLAS和MKL的性能在同一水平,唯一的例外是特征值测试。 在单线程模式下,特征值测试在OpenBLAS上的表现还算不错。 但在多线程模式下,性能就差了。
“矩阵大小与线程图表”也显示,虽然MKL和OpenBLAS通常在核心/线程数量上扩展良好,但这取决于矩阵的大小。对于小矩阵,增加更多核心不会显著提高性能。
从Sandy Bridge到Ivy Bridge的性能提升大约为30%,这可能是由于更高的时钟频率(+0.8 GHz)和/或更好的架构。
原始答案(2011年10月4日):
不久前,我需要优化一些用Python编写的线性代数计算/算法,这些算法使用了numpy和BLAS,所以我对不同的numpy/BLAS配置进行了基准测试。
具体来说,我测试了:
- Numpy与ATLAS
- Numpy与GotoBlas2(1.13)
- Numpy与MKL(11.1/073)
- Numpy与加速框架(Mac OS X)
我进行了两个不同的基准测试:
- 不同大小矩阵的简单点积
- 可以在这里找到的基准测试套件。
以下是我的结果:
机器
Linux(MKL、ATLAS、无MKL、GotoBlas2):
- 操作系统: Ubuntu Lucid 10.4 64位。
- CPU: 2 x 4 英特尔(R) 至强(R) E5504 @ 2.00GHz(8个核心)
- 内存: 24 GB
- 英特尔编译器: 11.1/073
- Scipy: 0.8
- Numpy: 1.5
Mac Book Pro(加速框架):
- 操作系统: Mac OS X Snow Leopard(10.6)
- CPU: 1 英特尔双核2.93 GHz(2个核心)
- 内存: 4 GB
- Scipy: 0.7
- Numpy: 1.3
Mac Server(加速框架):
- 操作系统: Mac OS X Snow Leopard Server(10.6)
- CPU: 4 x 英特尔(R) 至强(R) E5520 @ 2.26 GHz(8个核心)
- 内存: 4 GB
- Scipy: 0.8
- Numpy: 1.5.1
点积基准测试
代码:
import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)
结果:
System | size = 1000 | size = 2000 | size = 3000 | netlib BLAS | 1350 ms | 10900 ms | 39200 ms | ATLAS (1 CPU) | 314 ms | 2560 ms | 8700 ms | MKL (1 CPUs) | 268 ms | 2110 ms | 7120 ms | MKL (2 CPUs) | - | - | 3660 ms | MKL (8 CPUs) | 39 ms | 319 ms | 1000 ms | GotoBlas2 (1 CPU) | 266 ms | 2100 ms | 7280 ms | GotoBlas2 (2 CPUs)| 139 ms | 1009 ms | 3690 ms | GotoBlas2 (8 CPUs)| 54 ms | 389 ms | 1250 ms | Mac OS X (1 CPU) | 143 ms | 1060 ms | 3605 ms | Mac Server (1 CPU)| 92 ms | 714 ms | 2130 ms |
基准测试套件
代码:
有关基准测试套件的更多信息,请参见这里。
结果:
System | eigenvalues | svd | det | inv | dot | netlib BLAS | 1688 ms | 13102 ms | 438 ms | 2155 ms | 3522 ms | ATLAS (1 CPU) | 1210 ms | 5897 ms | 170 ms | 560 ms | 893 ms | MKL (1 CPUs) | 691 ms | 4475 ms | 141 ms | 450 ms | 736 ms | MKL (2 CPUs) | 552 ms | 2718 ms | 96 ms | 267 ms | 423 ms | MKL (8 CPUs) | 525 ms | 1679 ms | 60 ms | 137 ms | 197 ms | GotoBlas2 (1 CPU) | 2124 ms | 4636 ms | 147 ms | 456 ms | 743 ms | GotoBlas2 (2 CPUs)| 1560 ms | 3278 ms | 116 ms | 295 ms | 460 ms | GotoBlas2 (8 CPUs)| 741 ms | 2914 ms | 82 ms | 262 ms | 192 ms | Mac OS X (1 CPU) | 948 ms | 4339 ms | 151 ms | 318 ms | 566 ms | Mac Server (1 CPU)| 1033 ms | 3645 ms | 99 ms | 232 ms | 342 ms |
安装
安装MKL包括安装完整的英特尔编译器套件,这个过程相对简单。不过,由于一些错误/问题,配置和编译支持MKL的numpy有点麻烦。
GotoBlas2是一个小包,可以很容易地编译成共享库。不过,由于一个错误,你必须在构建后重新创建共享库才能与numpy一起使用。
此外,出于某种原因,针对多个目标平台的构建没有成功。因此,我不得不为每个平台创建一个.so文件,以便获得优化后的libgoto2.so文件。
如果你从Ubuntu的仓库安装numpy,它会自动安装并配置numpy以使用ATLAS。从源代码安装ATLAS可能需要一些时间,并且需要一些额外的步骤(如Fortran等)。
如果你在Mac OS X机器上使用Fink或Mac Ports安装numpy,它会将numpy配置为使用ATLAS或苹果的加速框架。 你可以通过运行ldd命令查看numpy.core._dotblas文件,或者调用numpy.show_config()来检查。
结论
MKL的表现最好,其次是GotoBlas2。
在特征值测试中,GotoBlas2的表现出乎意料地差,不知道为什么会这样。
苹果的加速框架在单线程模式下表现非常好(与其他BLAS实现相比)。
GotoBlas2和MKL在线程数量上扩展得很好。所以如果你需要处理大矩阵,使用多线程会有很大帮助。
无论如何,不要使用默认的netlib blas实现,因为它对于任何严肃的计算工作来说都太慢了。
在我们的集群上,我还安装了AMD的ACML,性能与MKL和GotoBlas2相似,但我没有具体数据。
我个人建议使用GotoBlas2,因为它更容易安装且是免费的。
如果你想用C++/C编程,也可以看看Eigen3,它在某些情况下的表现优于MKL/GotoBlas2,而且使用起来也很简单。
并不是NumPy里的所有功能都使用BLAS库,只有一些特定的函数,比如 dot()
、vdot()
和 innerproduct()
,还有一些来自 numpy.linalg
模块的函数。此外,要注意的是,对于大数组来说,很多NumPy操作受到内存带宽的限制,所以即使有优化的实现,也不太可能带来什么提升。如果你的内存带宽受到很大限制,使用多线程是否能提高性能,主要还是看你的硬件配置。