我正在为numpy编写一个新的随机数生成器,可以根据任意分布产生随机数。但是我发现了一个非常奇怪的行为:
这是test.pyx文件。
代码性能分析
二分查找实现需要执行
然而,即使这个没有计算的简单实现比numpy库中完整的二分搜索还要慢。(它是用C编写的:https://github.com/numpy/numpy/blob/202e78d607515e0390cffb1898e11807f117b36a/numpy/core/src/multiarray/item_selection.c请参见PyArray_SearchSorted)
结果如下:
为什么np.empty()步骤要花费如此多的时间?我该怎么做才能得到一个我可以返回的空数组?
C函数在内部循环中使用更长的算法并运行一堆健全性检查。 (我从示例中删除了除循环本身以外的所有逻辑)
更新:
事实证明有两个不同的问题:
1.仅调用np.empty(10)就具有巨大的开销,并且需要与searchsorted一起执行10次二分搜索,这需要花费相同的时间
2.仅声明缓冲区语法np.ndarray[...]也具有巨大的开销,它所需的时间比接收未经类型定义的变量并迭代50次还要长。
50次迭代结果:
这是test.pyx文件。
#cython: boundscheck=False
#cython: wraparound=False
import numpy as np
cimport numpy as np
cimport cython
def BareBones(np.ndarray[double, ndim=1] a,np.ndarray[double, ndim=1] u,r):
return u
def UntypedWithLoop(a,u,r):
cdef int i,j=0
for i in range(u.shape[0]):
j+=i
return u,j
def BSReplacement(np.ndarray[double, ndim=1] a, np.ndarray[double, ndim=1] u):
cdef np.ndarray[np.int_t, ndim=1] r=np.empty(u.shape[0],dtype=int)
cdef int i,j=0
for i in range(u.shape[0]):
j=i
return r
setup.py
from distutils.core import setup
from Cython.Build import cythonize
setup(name = "simple cython func",ext_modules = cythonize('test.pyx'),)
代码性能分析
#!/usr/bin/python
from __future__ import division
import subprocess
import timeit
#Compile the cython modules before importing them
subprocess.call(['python', 'setup.py', 'build_ext', '--inplace'])
sstr="""
import test
import numpy
u=numpy.random.random(10)
a=numpy.random.random(10)
a=numpy.cumsum(a)
a/=a[-1]
r=numpy.empty(10,int)
"""
print "binary search: creates an array[N] and performs N binary searches to fill it:\n",timeit.timeit('numpy.searchsorted(a,u)',sstr)
print "Simple replacement for binary search:takes the same args as np.searchsorted and similarly returns a new array. this performs only one trivial operation per element:\n",timeit.timeit('test.BSReplacement(a,u)',sstr)
print "barebones function doing nothing:",timeit.timeit('test.BareBones(a,u,r)',sstr)
print "Untyped inputs and doing N iterations:",timeit.timeit('test.UntypedWithLoop(a,u,r)',sstr)
print "time for just np.empty()",timeit.timeit('numpy.empty(10,int)',sstr)
二分查找实现需要执行
len(u)*Log(len(a))
的时间。简单的Cython函数需要执行len(u)
的时间才能运行。两者都返回长度为len(u)
的1D整数数组。然而,即使这个没有计算的简单实现比numpy库中完整的二分搜索还要慢。(它是用C编写的:https://github.com/numpy/numpy/blob/202e78d607515e0390cffb1898e11807f117b36a/numpy/core/src/multiarray/item_selection.c请参见PyArray_SearchSorted)
结果如下:
binary search: creates an array[N] and performs N binary searches to fill it:
1.15157485008
Simple replacement for binary search:takes the same args as np.searchsorted and similarly returns a new array. this performs only one trivial operation per element:
3.69442796707
barebones function doing nothing: 0.87496304512
Untyped inputs and doing N iterations: 0.244267940521
time for just np.empty() 1.0983929634
为什么np.empty()步骤要花费如此多的时间?我该怎么做才能得到一个我可以返回的空数组?
C函数在内部循环中使用更长的算法并运行一堆健全性检查。 (我从示例中删除了除循环本身以外的所有逻辑)
更新:
事实证明有两个不同的问题:
1.仅调用np.empty(10)就具有巨大的开销,并且需要与searchsorted一起执行10次二分搜索,这需要花费相同的时间
2.仅声明缓冲区语法np.ndarray[...]也具有巨大的开销,它所需的时间比接收未经类型定义的变量并迭代50次还要长。
50次迭代结果:
binary search: 2.45336699486
Simple replacement:3.71126317978
barebones function doing nothing: 0.924916028976
Untyped inputs and doing N iterations: 0.316384077072
time for just np.empty() 1.04949498177
import
和cimport
的numpy
命名为相同的名称时,会感到困惑,在scikits image中,他们通常会执行import numpy as np; cimport numpy as cnp
来区分它们。但我认为你在调用np.empty
时的np
是被import
的那个,而且没有被cimport
,所以这是一个Python函数调用,具有其众所周知的开销。你可以通过Cython调用PyArray_SimpleNew
来避免它,但不确定如何操作。如果你担心这种级别的优化,就放弃Cython,全程使用C-API吧... - Jaimenp.empty
是否会进行Python函数调用,这可能会解释开销,或者是Cython变体,这将表明Cython中的某些内容并不好。但我所编写的唯一Cython代码是来自文档的“Hello World!”:我发现它很令人困惑,主要是因为很难弄清楚某些东西是在快速的C中运行还是在缓慢的Python中运行,并且一直转向Python / NumPy C-API。因此,我的观点是有偏见的,而且不是很了解... - Jaimecython -a
很有帮助,它可以得到一个注释版本的代码,对调用 Python API 的行进行逐行着色,并允许您选择一行并查看相应生成的 C 代码。 - JoshAdel