问题:基准测试(使用BLAS的python与c ++)和(numpy)
我想编写一个程序,该程序广泛使用BLAS和LAPACK线性代数功能。由于性能是一个问题,因此我做了一些基准测试,想知道我采用的方法是否合法。
可以说,我有三个参赛者,并希望通过一个简单的矩阵矩阵乘法来测试他们的表现。参赛者是:
- Numpy,仅使用的功能
dot
。 - Python,通过共享对象调用BLAS功能。
- C ++,通过共享库调用BLAS功能。
情境
我为不同的尺寸实现了矩阵矩阵乘法i
。i
为5的增量和matricies运行5-500 m1
和m2
设置了这样的:
m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)
1.脾气暴躁
使用的代码如下所示:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))
2. Python,通过共享库调用BLAS
具有功能
_blaslib = ctypes.cdll.LoadLibrary("libblas.so")
def Mul(m1, m2, i, r):
no_trans = c_char("n")
n = c_int(i)
one = c_float(1.0)
zero = c_float(0.0)
_blaslib.sgemm_(byref(no_trans), byref(no_trans), byref(n), byref(n), byref(n),
byref(one), m1.ctypes.data_as(ctypes.c_void_p), byref(n),
m2.ctypes.data_as(ctypes.c_void_p), byref(n), byref(zero),
r.ctypes.data_as(ctypes.c_void_p), byref(n))
测试代码如下:
r = numpy.zeros((i,i), numpy.float32)
tBlas = timeit.Timer("Mul(m1, m2, i, r)", "import numpy; from __main__ import i, m1, m2, r, Mul")
rBlas.append((i, tBlas.repeat(20, 1)))
3. c ++,通过共享库调用BLAS
现在,c ++代码自然会更长一些,因此我将信息减少到最低限度。
我用
void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");
我这样测量时间gettimeofday
:
gettimeofday(&start, NULL);
f(&no_trans, &no_trans, &dim, &dim, &dim, &one, A, &dim, B, &dim, &zero, Return, &dim);
gettimeofday(&end, NULL);
dTimes[j] = CalcTime(start, end);
这里j
是运行20次的循环。我计算经过的时间
double CalcTime(timeval start, timeval end)
{
double factor = 1000000;
return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor;
}
结果
结果如下图所示:
问题
- 您认为我的方法是否公平,还是可以避免一些不必要的开销?
- 您是否希望结果显示出c ++和python方法之间的巨大差异?两者都使用共享对象进行计算。
- 由于我宁愿在程序中使用python,在调用BLAS或LAPACK例程时该如何做才能提高性能?
下载
完整的基准可以在这里下载。(塞巴斯蒂安(JF Sebastian)使该链接成为可能^^)
回答 0
我已经执行了您的基准测试。我的机器上C ++和numpy之间没有区别:
您认为我的方法是否公平,还是可以避免一些不必要的开销?
由于结果没有差异,因此看起来很公平。
您是否希望结果显示出c ++和python方法之间的巨大差异?两者都使用共享对象进行计算。
没有。
由于我宁愿在程序中使用python,在调用BLAS或LAPACK例程时该如何做才能提高性能?
确保numpy在系统上使用BLAS / LAPACK库的优化版本。
回答 1
更新(30.07.2014):
我在新的HPC上重新运行基准测试。硬件和软件堆栈都与原始答案中的设置有所不同。
我将结果放在Google电子表格中(还包含原始答案的结果)。
硬件
我们的HPC有两个不同的节点,一个带有Intel Sandy Bridge CPU,一个带有较新的Ivy Bridge CPU:
桑迪(MKL,OpenBLAS,ATLAS):
- CPU:2 x 16 Intel(R)Xeon(R)E2560 Sandy Bridge @ 2.00GHz(16核心)
- 内存:64 GB
常春藤(MKL,OpenBLAS,ATLAS):
- CPU:2.80GHz @ 2 x 20英特尔®至强®E2680 V2常春藤桥(20核,HT = 40核)
- 内存:256 GB
软件
该软件堆栈用于两个节点的sam。代替GotoBLAS2,OpenBLAS被使用并且也有一个多线程的ATLAS BLAS它被设置为8个线程(硬编码)。
- 操作系统:Suse
- 英特尔编译器:ictce-5.3.0
- 脾气暴躁的: 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在单线程模式。在多线程模式下,性能较差。
的“矩阵大小VS线程图表”也表明,虽然MKL以及OpenBLAS通常与核/线程的数量很好地扩展,这取决于基质的大小。对于较小的矩阵,添加更多内核不会大大提高性能。
从Sandy Bridge到Ivy Bridge的性能也提高了大约30%,这可能是由于更高的时钟速率(+ 0.8 Ghz)和/或更好的体系结构所致。
原始答案(04.10.2011):
前段时间,我不得不优化一些使用numpy和BLAS用python编写的线性代数计算/算法,因此我对不同的numpy / BLAS配置进行了基准测试。
我专门测试了:
- 用ATLAS调皮
- Numpy与GotoBlas2(1.13)
- 用MKL调皮(11.1 / 073)
- Numpy with Accelerate Framework(Mac OS X)
我确实运行了两个不同的基准测试:
- 大小不同的矩阵的简单点积
- 基准套件可在此处找到。
这是我的结果:
机器
Linux(MKL,ATLAS,No-MKL,GotoBlas2):
- 操作系统:Ubuntu Lucid 10.4 64 Bit。
- CPU:2 x 4英特尔(R)至强(R)E5504 @ 2.00GHz(8核)
- 内存:24 GB
- 英特尔编译器:11.1 / 073
- Scipy:0.8
- 脾气暴躁的:1.5
Mac Book Pro(加速框架):
- 操作系统:Mac OS X Snow Leopard(10.6)
- CPU:1个Intel Core 2 Duo 2.93 Ghz(2个内核)
- 内存:4 GB
- 西皮:0.7
- 脾气暴躁的:1.3
Mac Server(加速框架):
- 操作系统:Mac OS X Snow Leopard Server(10.6)
- CPU:4 X Intel(R)Xeon(R)E5520 @ 2.26 Ghz(8核)
- 内存:4 GB
- Scipy:0.8
- 脾气暴躁的: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)
结果:
系统| 大小= 1000 | 大小= 2000 | 大小= 3000 | netlib BLAS | 1350毫秒| 10900毫秒| 39200毫秒| ATLAS(1 CPU)| 314毫秒| 2560毫秒| 8700毫秒| MKL(1 CPU)| 268毫秒| 2110毫秒| 7120毫秒| MKL(2个CPU)| -| -| 3660毫秒| MKL(8个CPU)| 39毫秒| 319毫秒| 1000毫秒| GotoBlas2(1 CPU)| 266毫秒| 2100毫秒| 7280毫秒| GotoBlas2(2个CPU)| 139毫秒| 1009毫秒| 3690毫秒| GotoBlas2(8个CPU)| 54毫秒| 389毫秒| 1250毫秒| Mac OS X(1个CPU)| 143毫秒| 1060毫秒| 3605毫秒| Mac服务器(1个CPU)| 92毫秒| 714毫秒| 2130毫秒|
基准套件
代码:
有关基准套件的更多信息,请参见此处。
结果:
系统| 特征值| svd | det | inv | 点| netlib BLAS | 1688毫秒| 13102毫秒| 438毫秒| 2155毫秒| 3522毫秒| ATLAS(1 CPU)| 1210毫秒| 5897毫秒| 170毫秒| 560毫秒| 893毫秒| MKL(1 CPU)| 691毫秒| 4475毫秒| 141毫秒| 450毫秒| 736毫秒| MKL(2个CPU)| 552毫秒| 2718毫秒| 96毫秒| 267毫秒| 423毫秒| MKL(8个CPU)| 525毫秒| 1679毫秒| 60毫秒| 137毫秒| 197毫秒| GotoBlas2(1 CPU)| 2124毫秒| 4636毫秒| 147毫秒| 456毫秒| 743毫秒| GotoBlas2(2个CPU)| 1560毫秒| 3278毫秒| 116毫秒| 295毫秒| 460毫秒| GotoBlas2(8个CPU)| 741毫秒| 2914毫秒| 82毫秒| 262毫秒| 192毫秒| Mac OS X(1个CPU)| 948毫秒| 4339毫秒| 151毫秒| 318毫秒| 566毫秒| Mac服务器(1个CPU)| 1033毫秒| 3645毫秒| 99毫秒| 232毫秒| 342毫秒|
安装
安装MKL包括安装完整的英特尔编译器套件,这是相当直截了当。但是,由于存在一些错误/问题,使用MKL支持配置和编译numpy有点麻烦。
GotoBlas2是一个小软件包,可以轻松地编译为共享库。但是,由于存在错误,您必须在构建共享库后重新创建共享库才能与numpy一起使用。
除了这种构建之外,由于某些原因,它无法用于多个目标平台。因此,我必须为每个平台都创建一个.so文件,我要为其提供优化的libgoto2.so文件。
如果您从Ubuntu的存储库中安装numpy,它将自动安装并配置numpy以使用ATLAS。从源代码安装ATLAS可能需要一些时间,并且需要一些其他步骤(fortran等)。
如果您在具有Fink或Mac Ports的Mac OS X机器上安装numpy,它将配置numpy以使用ATLAS或Apple的Accelerate Framework。您可以通过在numpy.core._dotblas文件上运行ldd 或调用numpy.show_config()进行检查。
结论
MKL紧随其后的是GotoBlas2。
在特征值测试中,GotoBlas2的表现令人惊讶地比预期的差。不知道为什么会这样。
Apple的Accelerate Framework的性能非常好,特别是在单线程模式下(与其他BLAS实现相比)。
GotoBlas2和MKL都可以很好地随线程数扩展。因此,如果您必须处理在多个线程上运行的大型矩阵,将会很有帮助。
无论如何都不要使用默认的netlib blas实现,因为它对于任何严肃的计算工作来说都太慢了。
在我们的集群上,我还安装了AMD的ACML,性能类似于MKL和GotoBlas2。我没有任何强硬的数字。
我个人建议使用GotoBlas2,因为它更容易安装且免费。
如果您想用C ++ / C进行编码,还可以查看Eigen3,它在某些情况下应该胜过MKL / GotoBlas2,并且非常易于使用。
回答 2
这是另一个基准测试(在Linux上,只需输入make
):http : //dl.dropbox.com/u/5453551/blas_call_benchmark.zip
http://dl.dropbox.com/u/5453551/blas_call_benchmark.png
我看不到大型矩阵的不同方法,Numpy,Ctypes和Fortran之间的任何区别。(Fortran而不是C ++ —如果这很重要,则您的基准可能已损坏。)
您也许您的基准测试还存在其他错误,例如,在不同的BLAS库之间进行比较,或在不同的BLAS设置(例如线程数)之间进行比较,或者在实时与CPU时间之间进行比较?CalcTime
在C ++中的函数似乎有符号错误。... + ((double)start.tv_usec))
应该代替... - ((double)start.tv_usec))
。
编辑:无法计算CalcTime
函数中的花括号-可以。
作为准则:如果进行基准测试,请始终将所有代码发布到某个地方。在没有完整代码的情况下对基准进行注释,尤其是在令人惊讶的情况下,通常是无效的。
要找出链接到哪个BLAS Numpy,请执行以下操作:
$Python Python 2.7.2+(默认值,2011年8月16日,07:24:41) linux2上的[GCC 4.6.1] 键入“帮助”,“版权”,“信用”或“许可证”以获取更多信息。 >>>导入numpy.core._dotblas >>> numpy.core._dotblas .__ file__ '/usr/lib/pymodules/python2.7/numpy/core/_dotblas.so' >>> $ ldd /usr/lib/pymodules/python2.7/numpy/core/_dotblas.so linux-vdso.so.1 =>(0x00007fff5ebff000) libblas.so.3gf => /usr/lib/libblas.so.3gf(0x00007fbe618b3000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6(0x00007fbe61514000)
更新:如果您无法导入numpy.core._dotblas,则您的Numpy正在使用其内部的BLAS后备副本,该副本速度较慢,并且不能用于性能计算!下面来自@Woltan的答复表明,这是他/她在Numpy与Ctypes + BLAS中看到的差异的解释。
要解决这种情况,您需要ATLAS或MKL —查看以下说明:http : //scipy.org/Installing_SciPy/Linux 大多数Linux发行版都随ATLAS一起提供,因此最好的选择是安装其libatlas-dev
软件包(名称可能有所不同) 。
回答 3
考虑到您对分析的严格要求,迄今为止的结果令我感到惊讶。我将其作为“答案”,但这仅是因为评论时间太长并且确实提供了可能性(尽管我希望您已经考虑过)。
我本来认为numpy / python方法不会为合理复杂度的矩阵增加太多开销,因为随着复杂度的增加,python参与的比例应该很小。我对图右侧的结果更感兴趣,但显示出数量级差异会令人不安。
我想知道您是否正在使用numpy可以利用的最佳算法。从Linux的编译指南中:
“构建FFTW(3.1.2):SciPy版本> = 0.7和Numpy> = 1.2:由于许可证,配置和维护问题,在SciPy> = 0.7和NumPy> = 1.2的版本中,不再支持FFTW。现在使用fftpack的内置版本。如果需要进行分析,有几种方法可以利用FFTW的速度;降级到包含支持的Numpy / Scipy版本;安装或创建自己的FFTW包装器。请参阅http: //developer.berlios.de/projects/pyfftw/作为未经认可的示例。”
你用mkl编译numpy吗?(http://software.intel.com/zh-cn/articles/intel-mkl/)。如果您在Linux上运行,则使用mkl编译numpy的说明如下:http : //www.scipy.org/Installing_SciPy/Linux#head-7ce43956a69ec51c6f2cedd894a4715d5bfff974(尽管有url)。关键部分是:
[mkl]
library_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/lib/intel64
include_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/include
mkl_libs = mkl_intel_lp64,mkl_intel_thread,mkl_core
如果您使用的是Windows,则可以通过以下网址使用mkl获得编译的二进制文件(并且还可以获取pyfftw和许多其他相关算法):http ://www.lfd.uci.edu/~gohlke/pythonlibs/ ,其中包含感谢UC Irvine荧光动力学实验室的Christoph Gohlke。
需要注意的是,无论哪种情况,都有许多许可问题等需要注意的地方,但是intel页面对此进行了解释。同样,我想您已经考虑了这一点,但是如果满足许可要求(在Linux上很容易做到),相对于使用简单的自动构建(甚至不使用FFTW),这将大大加快numpy的工作。我将有兴趣关注这个话题,看看其他人的想法。无论如何,都非常严格,也有很好的问题。感谢您发布。