我想编写一个程序,该程序广泛使用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)使该链接成为可能^^)
r
矩阵的内存分配不公平。我现在正在解决“问题”并发布新结果。
np.ascontiguousarray()
(考虑C与Fortran顺序)。2.确保np.dot()
使用相同的libblas.so
。
m1
和m2
具有ascontiguousarray
标志作为True
。numpy与C使用相同的共享库。至于数组的顺序:目前,我对计算结果不感兴趣,因此顺序无关紧要。