更新
不幸的是,由于我的疏忽,我有一个旧版本的MKL(11.1)与numpy相关联。更新版本的MKL(11.3.1)在C语言中以及从python调用时都具有相同的性能。在
使事情变得模糊不清的是,即使将编译好的共享库显式地与新的MKL链接起来,并通过LD*variables指向它们,然后在python中执行import numpy,也会使python调用旧的MKL库。只有在python lib文件夹中替换所有libmkl_*.so,我才能够匹配python和C调用的性能。在
背景/库信息。
矩阵乘法是通过sgemm(单精度)和dgemm(双精度)Intel的MKL库调用,通过纽比.dot功能。库函数的实际调用可以用例如oprof进行验证。在
这里使用2x18核的CPU E5-2699 v3,因此总共有36个物理内核。
KMP_AFFINITY=分散。在linux上运行。在
TL;DR
1)为什么纽比.dot,即使它调用相同的MKL库函数,但与C编译的代码相比最多慢两倍?在
2)为什么选择via纽比.dot随着核心数量的增加,性能会下降,而在C代码中(调用相同的库函数)却没有观察到相同的效果。在
问题
我观察到,做单精度/双精度的矩阵乘法纽比.dot,以及直接从编译的C共享库调用cblas sgemm/dgemm,与从纯C代码内部调用相同的MKL cblas sgemm/dgemm函数相比,性能明显较差。在import numpy as np
import mkl
n = 10000
A = np.random.randn(n,n).astype('float32')
B = np.random.randn(n,n).astype('float32')
C = np.zeros((n,n)).astype('float32')
mkl.set_num_threads(3); %time np.dot(A, B, out=C)
11.5 seconds
mkl.set_num_threads(6); %time np.dot(A, B, out=C)
6 seconds
mkl.set_num_threads(12); %time np.dot(A, B, out=C)
3 seconds
mkl.set_num_threads(18); %time np.dot(A, B, out=C)
2.4 seconds
mkl.set_num_threads(24); %time np.dot(A, B, out=C)
3.6 seconds
mkl.set_num_threads(30); %time np.dot(A, B, out=C)
5 seconds
mkl.set_num_threads(36); %time np.dot(A, B, out=C)
5.5 seconds
执行与上述完全相同的操作,但使用双精度A、B和C,可以得到:
3芯20s,6芯10s,12芯5s,18芯4.3s,24芯3s,30芯2.8s,36芯2.8s
单精度浮点的速度增加似乎与缓存未命中有关。
对于28核运行,这里是perf的输出。
对于单精度:
^{pr2}$
双精度:93,087,703 cache-misses # 5.164 % of all cache refs
C共享库,用/opt/intel/bin/icc -o comp_sgemm_mkl.so -openmp -mkl sgem_lib.c -lm -lirc -O3 -fPIC -shared -std=c99 -vec-report1 -xhost -I/opt/intel/composer/mkl/include
#include
#include
#include "mkl.h"
void comp_sgemm_mkl(int m, int n, int k, float *A, float *B, float *C);
void comp_sgemm_mkl(int m, int n, int k, float *A, float *B, float *C)
{
int i, j;
float alpha, beta;
alpha = 1.0; beta = 0.0;
cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans,
m, n, k, alpha, A, k, B, n, beta, C, n);
}
Python包装器函数,调用上面编译的库:def comp_sgemm_mkl(A, B, out=None):
lib = CDLL(omplib)
lib.cblas_sgemm_mkl.argtypes = [c_int, c_int, c_int,
np.ctypeslib.ndpointer(dtype=np.float32, ndim=2),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=2),
np.ctypeslib.ndpointer(dtype=np.float32, ndim=2)]
lib.comp_sgemm_mkl.restype = c_void_p
m = A.shape[0]
n = B.shape[0]
k = B.shape[1]
if np.isfortran(A):
raise ValueError('Fortran array')
if m != n:
raise ValueError('Wrong matrix dimensions')
if out is None:
out = np.empty((m,k), np.float32)
lib.comp_sgemm_mkl(m, n, k, A, B, out)
然而,从一个调用MKL的cblas_sgemm/cblas_ggemm的C编译二进制文件中显式调用,以及在C中通过malloc分配的数组,与python代码相比,性能提高了近2倍,即纽比.dot打电话。此外,还没有观察到随核心数量增加而导致性能下降的影响。单精度矩阵乘法的最佳性能为900ms,通过mkl_set_num_cores使用所有36个物理内核,并使用numactl--interleave=all运行C代码时,性能达到最佳。在
也许有什么新奇的工具或建议来进一步分析/检查/了解这种情况?任何阅读材料也很受欢迎。在
更新
遵循@Hristo Iliev的建议,运行numactl--interleave=all./ipython没有更改计时(在noise中),但改进了纯C二进制运行时。在