使用 cython 分配到任意数组位置。赋值速度取决于值?
assignment into arbitrary array locations using cython. assignment speed depends on value?
我在 cython 代码中看到一些奇怪的行为。我正在编写代码来计算前向卡尔曼滤波器,但我有一个状态转换模型,其中包含许多 0
,因此能够仅计算协方差矩阵的某些元素会很好。
因此,为了对此进行测试,我想使用 cython 填充单个数组元素。令我惊讶的是,我发现
将输出写入特定数组位置非常慢 (function fill(...))
,与每次都将其分配给标量变量 (function nofill(...))
(基本上忘记结果)相比,并且
设置C=0.1
或31
,虽然不影响nofill(...)
到运行的时间,但C的后一个选择使fill(...)
运行 慢 2 倍。这让我感到莫名其妙。谁能解释为什么我会看到这个?
代码:-
################# file way_too_slow.pyx
from libc.math cimport sin
# Setting C=0.1 or 31 doesn't change affect performance of calling nofill(...), but it makes the fill(...) slower. I have no clue why.
cdef double C = 0.1
# This function just throws away its output.
def nofill(double[::1] x, double[::1] y, long N):
cdef int i
cdef double *p_x = &x[0]
cdef double *p_y = &y[0]
cdef double d
with nogil:
for 0 <= i < N:
d = ((p_x[i] + p_y[i])*3 + p_x[i] - p_y[i]) + sin(p_x[i]*C) # C appears here
# Same function keeps its output.
# However: #1 - MUCH slower than
def fill(double[::1] x, double[::1] y, double[::1] out, long N):
cdef int i
cdef double *p_x = &x[0]
cdef double *p_y = &y[0]
cdef double *p_o = &out[0]
cdef double d
with nogil:
for 0 <= i < N:
p_o[i] = ((p_x[i] + p_y[i])*3 + p_x[i] - p_y[i]) + sin(p_x[i]*C) # C appears here
以上代码被python程序调用
#################### run_way_too_slow.py
import way_too_slow as _wts
import time as _tm
N = 80000
x = _N.random.randn(N)
y = _N.random.randn(N)
out = _N.empty(N)
t1 = _tm.time()
_wts.nofill(x, y, N)
t2 = _tm.time()
_wts.fill(x, y, out, N)
t3 = _tm.time()
print "nofill() ET: %.3e" % (t2-t1)
print "fill() ET: %.3e" % (t3-t2)
print "fill() is slower by factor %.3f" % ((t3-t2)/(t2-t1))
cython 是使用 setup.py 文件编译的
################# setup.py
from distutils.core import setup, Extension
from distutils.sysconfig import get_python_inc
from distutils.extension import Extension
from Cython.Distutils import build_ext
incdir=[get_python_inc(plat_specific=1)]
libdir = ['/usr/local/lib']
cmdclass = {'build_ext' : build_ext}
ext_modules = Extension("way_too_slow",
["way_too_slow.pyx"],
include_dirs=incdir, # include_dirs for Mac
library_dirs=libdir)
setup(
name="way_too_slow",
cmdclass = cmdclass,
ext_modules = [ext_modules]
)
这是使用 C=0.1
的 运行ning "run_way_too_slow.py" 的典型输出
>>> exf("run_way_too_slow.py")
nofill() ET: 6.700e-05
fill() ET: 6.409e-04
fill() is slower by factor 9.566
C=31 的典型 运行。
>>> exf("run_way_too_slow.py")
nofill() ET: 6.795e-05
fill() ET: 1.566e-03
fill() is slower by factor 23.046
如我们所见
与分配给双精度数相比,分配到指定的数组位置非常慢。
出于某种原因,分配速度似乎取决于计算中进行的操作 - 这对我来说毫无意义。
如有任何见解,我们将不胜感激。
有两件事可以解释您的观察结果:
A:在第一个版本中没有任何反应。 c 编译器足够聪明,可以看出整个循环在函数之外根本没有影响,并对其进行了优化。
要强制执行,您必须使结果 d
在外部可见,例如通过:
cdef double d=0
....
d+=....
return d
它可能仍然比 writing-to-an-array-version 慢,因为内存访问成本较低 - 但在更改值 C
.
时您会看到速度变慢
B: sin
是一个复杂的函数,计算需要多长时间取决于它的参数。例如,对于非常小的参数 - 可以返回参数本身,但对于更大的参数,必须评估更长的泰勒级数。 是 tanh
成本的一个示例,具体取决于参数的值,这与 sin
一样是通过不同的近似值/泰勒级数计算的 - 所需时间最重要的部分取决于参数。
我在 cython 代码中看到一些奇怪的行为。我正在编写代码来计算前向卡尔曼滤波器,但我有一个状态转换模型,其中包含许多 0
,因此能够仅计算协方差矩阵的某些元素会很好。
因此,为了对此进行测试,我想使用 cython 填充单个数组元素。令我惊讶的是,我发现
将输出写入特定数组位置非常慢
(function fill(...))
,与每次都将其分配给标量变量(function nofill(...))
(基本上忘记结果)相比,并且设置
C=0.1
或31
,虽然不影响nofill(...)
到运行的时间,但C的后一个选择使fill(...)
运行 慢 2 倍。这让我感到莫名其妙。谁能解释为什么我会看到这个?
代码:-
################# file way_too_slow.pyx
from libc.math cimport sin
# Setting C=0.1 or 31 doesn't change affect performance of calling nofill(...), but it makes the fill(...) slower. I have no clue why.
cdef double C = 0.1
# This function just throws away its output.
def nofill(double[::1] x, double[::1] y, long N):
cdef int i
cdef double *p_x = &x[0]
cdef double *p_y = &y[0]
cdef double d
with nogil:
for 0 <= i < N:
d = ((p_x[i] + p_y[i])*3 + p_x[i] - p_y[i]) + sin(p_x[i]*C) # C appears here
# Same function keeps its output.
# However: #1 - MUCH slower than
def fill(double[::1] x, double[::1] y, double[::1] out, long N):
cdef int i
cdef double *p_x = &x[0]
cdef double *p_y = &y[0]
cdef double *p_o = &out[0]
cdef double d
with nogil:
for 0 <= i < N:
p_o[i] = ((p_x[i] + p_y[i])*3 + p_x[i] - p_y[i]) + sin(p_x[i]*C) # C appears here
以上代码被python程序调用
#################### run_way_too_slow.py
import way_too_slow as _wts
import time as _tm
N = 80000
x = _N.random.randn(N)
y = _N.random.randn(N)
out = _N.empty(N)
t1 = _tm.time()
_wts.nofill(x, y, N)
t2 = _tm.time()
_wts.fill(x, y, out, N)
t3 = _tm.time()
print "nofill() ET: %.3e" % (t2-t1)
print "fill() ET: %.3e" % (t3-t2)
print "fill() is slower by factor %.3f" % ((t3-t2)/(t2-t1))
cython 是使用 setup.py 文件编译的
################# setup.py
from distutils.core import setup, Extension
from distutils.sysconfig import get_python_inc
from distutils.extension import Extension
from Cython.Distutils import build_ext
incdir=[get_python_inc(plat_specific=1)]
libdir = ['/usr/local/lib']
cmdclass = {'build_ext' : build_ext}
ext_modules = Extension("way_too_slow",
["way_too_slow.pyx"],
include_dirs=incdir, # include_dirs for Mac
library_dirs=libdir)
setup(
name="way_too_slow",
cmdclass = cmdclass,
ext_modules = [ext_modules]
)
这是使用 C=0.1
的 运行ning "run_way_too_slow.py" 的典型输出>>> exf("run_way_too_slow.py")
nofill() ET: 6.700e-05
fill() ET: 6.409e-04
fill() is slower by factor 9.566
C=31 的典型 运行。
>>> exf("run_way_too_slow.py")
nofill() ET: 6.795e-05
fill() ET: 1.566e-03
fill() is slower by factor 23.046
如我们所见
与分配给双精度数相比,分配到指定的数组位置非常慢。
出于某种原因,分配速度似乎取决于计算中进行的操作 - 这对我来说毫无意义。
如有任何见解,我们将不胜感激。
有两件事可以解释您的观察结果:
A:在第一个版本中没有任何反应。 c 编译器足够聪明,可以看出整个循环在函数之外根本没有影响,并对其进行了优化。
要强制执行,您必须使结果 d
在外部可见,例如通过:
cdef double d=0
....
d+=....
return d
它可能仍然比 writing-to-an-array-version 慢,因为内存访问成本较低 - 但在更改值 C
.
B: sin
是一个复杂的函数,计算需要多长时间取决于它的参数。例如,对于非常小的参数 - 可以返回参数本身,但对于更大的参数,必须评估更长的泰勒级数。 tanh
成本的一个示例,具体取决于参数的值,这与 sin
一样是通过不同的近似值/泰勒级数计算的 - 所需时间最重要的部分取决于参数。