我正在尝试获得针对cython和numpy的FLOPS基准测试。为此,我在cython中编写了一个程序。这是:
cimport numpy as np
import numpy as np
import time
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def numpybenchmark():
cdef np.ndarray[np.float64_t, ndim=2] m1 = np.random.rand(3,3)
cdef np.ndarray[np.float64_t, ndim=1] m2 = np.random.rand(3)
cdef np.ndarray[np.float64_t, ndim=1] res
cdef int niters = 10000000
cdef int x
t1 = time.time()
for x in range(niters):
res = np.dot(m1, m2)
t2 = time.time()
cdef double numopsperloop = 9. + 6.
cdef double totalops = numopsperloop * float(niters)
cdef double mflops = totalops / (t2-t1) / 1024. / 1024.
print 'Computed MFLops is: ' + str(mflops)
在我的机器上,我测量“Computed MFLops is:7.42390102416”。我的机器配备Intel Core i7-6700HQ CPU @ 2.6 GHz并运行Windows 10。
如果要在计算机上运行它,请将代码保存在名为“benchmark.pyx”的文件中。然后创建一个名为“setup.py”的文件,其中包含以下内容:
from distutils.core import setup
from Cython.Build import cythonize
import numpy
setup(
ext_modules = cythonize("benchmark.pyx"),
include_dirs=[numpy.get_include()]
)
然后你应该能够用“python setup.py build_ext --inplace”编译它。在Windows上可能会有点困难,因为我遇到了可怕的“无法找到vcvarsall.bat”错误,并且不得不花费大量精力来解决这个问题。
这种表现对我来说似乎很糟糕。我想知道是否有人可以在他们的平台上运行它并告诉我你得到了什么?或指出我的代码中对性能产生负面影响的任何明显错误?
谢谢!
答案 0 :(得分:2)
Cython实际上并没有消除np.dot
上的任何Python调用开销。这涉及(请注意,该列表并非详尽无遗,并且在某些地方可能略有错误,但它给出了要点):
查找np.dot
来致电:
np
np
dot
命名空间中的词典查找。 (注意,通过在函数内部执行dot = np.dot
,然后调用dot
)dot
__call__
上的字典查找。 (如果dot是C / Fortran编译函数,可以通过更快的机制完成)打包准备np.dot
的参数:
np.dot
np.dot
然后处理参数......
dtype
是否相同,并根据BLAS函数调用的dtype
选项进行检查。...为输出参数分配空间......
np.ndarray
对象ndarray
...调用BLAS操作,为您提供浮点操作...
...并减少传递的输入参数的引用计数(检查是否应该释放任何参数,但不会有任何参数)
您的通话功能必须:
np.dot
res
res
的先前内容,记住它至少是一个两步过程,因为该数组与ndarray
持有者分开存放。如果你想使大部分(除了可能的分配)与矩阵向量乘法相比无关紧要,那么你需要对明显更大的数组进行测量。您可以使用out
中的np.dot
可选参数来摆脱分配。如果你想让它全部消失,那么你可以使用scipy Cython BLAS interface直接调用BLAS函数。
答案 1 :(得分:1)
在仔细阅读DavidW的帖子并做了一些实验之后,我找到了一种方法来避免所有笨拙的开销。它涉及使用指针,特别是不将numpy数组传递给循环内的函数。
以下是完整代码:
cimport numpy as np
import numpy as np
import time
cdef matrixdotvector(double* mat, int numrows, int numcols, double* vec, double* outputvec):
outputvec[0] = mat[0+0*numcols] * vec[0] + mat[1+0*numcols] * vec[1] + mat[2+0*numcols] * vec[2]
outputvec[1] = mat[0+1*numcols] * vec[0] + mat[1+1*numcols] * vec[1] + mat[2+1*numcols] * vec[2]
outputvec[2] = mat[0+2*numcols] * vec[0] + mat[1+2*numcols] * vec[1] + mat[2+2*numcols] * vec[2]
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def numpybenchmark():
cdef np.ndarray[np.float64_t, ndim=2] m1 = np.random.rand(3,3)
cdef np.ndarray[np.float64_t, ndim=1] m2 = np.transpose(np.random.rand(3))
cdef np.ndarray[np.float64_t, ndim=1] res
cdef int niters = 10000000
cdef int x
t1 = time.time()
for x in range(niters):
res = np.dot(m1, m2)
t2 = time.time()
cdef double numopsperloop = 9. + 6.
cdef double totalops = numopsperloop * float(niters)
cdef double mflops = totalops / (t2-t1) / 1024. / 1024.
print 'Computed MFLops is: ' + str(mflops)
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def numpybenchmark2():
cdef int numrows = 3
cdef int numcols = 3
cdef np.ndarray[np.float64_t, ndim=2] m1 = np.random.rand(3,3)
cdef np.ndarray[np.float64_t, ndim=1] m2 = np.transpose(np.random.rand(3))
cdef np.ndarray[np.float64_t, ndim=1] res = np.zeros(3)
cdef int niters = 10000000
cdef int x
t1 = time.time()
for x in range(niters):
matrixdotvector(&m1[0,0], numrows, numcols, &m2[0], &res[0])
t2 = time.time()
assert (np.linalg.norm(np.dot(m1,m2) - res) < 1.0e-6), "Arrays do not match"
cdef double numopsperloop = 9. + 6.
cdef double totalops = numopsperloop * float(niters)
cdef double mflops = totalops / (t2-t1) / 1024. / 1024.
print 'Computed MFLops is: ' + str(mflops)
numpybenchmark()和numpybenchmark2()之间的最大区别在于我通过将指针传递给numpybenchmark2()中的numpy数据数组来避免所有numpy开销(而不是传递类型化的numpy对象,这也很慢) 。我通过展开它并在代码中重新实现它来避免np.dot计算的开销。
所以我现在得到的基准测试结果是:
在[13]中:benchmark.numpybenchmark() 计算的MFLops是:7.3412268815
在[14]中:benchmark.numpybenchmark2() 计算的MFLops是:1521.81908107
所以这是一个相当大的增长。老实说,这不是一种“pythonic”方式,但它快速尖叫,所以在某些情况下可能有用。由于matrixdotvector()中的代码看起来像C代码,因此可以认为这应该全部用C语言编写。就个人而言,我发现使用类似C语言的cython代码实现原型更快,而不是直接进入C语言。
无论如何,也许这篇文章对某些正在学习cython的人来说是有用的。