【问题标题】:assignment into arbitrary array locations using cython. assignment speed depends on value?使用 cython 分配到任意数组位置。分配速度取决于价值?
【发布时间】:2018-08-11 07:50:00
【问题描述】:

我在 代码中看到了一些奇怪的行为。我正在编写代码来计算前向卡尔曼滤波器,但我有一个状态转换模型,其中包含许多 0s,因此能够仅计算协方差矩阵的某些元素会很好。

所以为了测试这一点,我想使用 填充单个数组元素。令我惊讶的是,我发现了

  1. 将输出写入特定数组位置非常慢(function fill(...)),与每次仅将其分配给一个标量变量相比(function nofill(...))(基本上忘记了结果),并且

  2. 设置C=0.131,虽然不影响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 运行“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

我们可以看到

  1. 与分配给 double 相比,分配到指定的数组位置非常慢。

  2. 出于某种原因,分配速度似乎取决于计算中执行的操作 - 这对我来说毫无意义。

任何见解将不胜感激。

【问题讨论】:

    标签: cython cython python c arrays cython variable-assignment


    【解决方案1】:

    有两件事可以解释你的观察:

    A:在第一个版本中没有任何反应。 c 编译器足够聪明,可以看出整个循环在函数之外根本没有任何影响,并对其进行了优化。

    要强制执行,您必须使结果 d 在外部可见,例如通过:

    cdef double d=0
    ....
           d+=....
    return d
    

    它可能仍然比写入数组版本慢,因为内存访问成本更低 - 但在更改值 C 时您会看到速度变慢。

    B:sin 是一个复杂的函数,计算需要多长时间取决于它的参数。例如,对于非常小的参数 - 可以返回参数本身,但对于较大的参数,必须评估更长的泰勒级数。 Heretanh 成本的一个示例,具体取决于参数的值,就像 sin 一样,通过不同的近似值/泰勒级数计算 - 所需时间最重要的部分取决于参数。

    【讨论】:

    • 谢谢! A)部分是让一切变得有意义的关键。我没有意识到编译器如此聪明,但是当我按照您的建议使循环实际执行某些操作时,运行时就像预期的那样变得可比。由于 A) 部分发生的事情,C 的值在 nofill(...) 中没有影响,因为该代码可能根本没有运行,而对于 fill(...),sin 的运行时成本被直接反映了。
    猜你喜欢
    • 1970-01-01
    • 2015-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-27
    • 2015-07-13
    • 2019-01-20
    相关资源
    最近更新 更多