performance - 为什么我的 Cython C 函数比它包装的内置函数慢 40 倍?

标签 performance cython

我是 Cython 新手。为什么我的 C 函数 Numeraire(它此时只是包装了一个内置函数)比直接调用内置函数慢得多?

谢谢。这是 Cython 代码 (backward.pyx) 代码:

import numpy as np
cimport numpy as np

from libc.math cimport exp

cdef double Numeraire(int i1, int i0, np.ndarray[np.int_t, ndim=1] j):
    cdef float rate = 0.05
    return exp(-rate/12*(i1 - i0))

def Slow(np.ndarray[np.float_t, ndim=2] values, int i1, int i0):
    cdef float norm = 0.25
    cdef int i, j0, j1
    cdef np.ndarray[np.int_t, ndim=1] j = np.empty(2, dtype=np.int)
    for i in range(i1-1, i0-1, -1):
        for j0 in range(i+1):
            j[0] = j0
            for j1 in range(i+1):
                j[1] = j1
                values[j0, j1] += (
                    values[j0+1, j1  ] +
                    values[j0  , j1+1] +
                    values[j0+1, j1+1])
                values[j0, j1] *= norm*Numeraire(i+1, i, j)      #4.397s (!)

def Fast(np.ndarray[np.float_t, ndim=2] values, int i1, int i0):
    cdef float norm = 0.25
    cdef int i, j0, j1
    cdef np.ndarray[np.int_t, ndim=1] j = np.empty(2, dtype=np.int)
    for i in range(i1-1, i0-1, -1):
        for j0 in range(i+1):
            j[0] = j0
            for j1 in range(i+1):
                j[1] = j1
                values[j0, j1] += (
                    values[j0+1, j1  ] +
                    values[j0  , j1+1] +
                    values[j0+1, j1+1])
                values[j0, j1] *= norm*exp(-0.05/12*((i+1) - i)) #0.327s

这是时间信息:

In [1]: import numpy as np
In [2]: import backward
In [3]: factors=2
In [4]: i=360
In [5]: %timeit backward.Fast(np.ones([i+1]*factors), i, 0)
10 loops, best of 3: 104 ms per loop
In [6]: %timeit backward.Slow(np.ones([i+1]*factors), i, 0)
1 loops, best of 3: 4.67 s per loop

最佳答案

它与ndarray有关您正在传递给 Numeraire 而不是使用。如果你运行cython -a backward.pyx看看你首先看到的代码 cdef double Numeraire...该行突出显示为浅黄色(表明 Cython 正在那里执行隐藏工作),当您单击该行时,您会得到以下代码

static double __pyx_f_8backward_Numeraire(int __pyx_v_i1, int __pyx_v_i0, CYTHON_UNUSED PyArrayObject *__pyx_v_j) {
  float __pyx_v_rate;
  __Pyx_LocalBuf_ND __pyx_pybuffernd_j;
  __Pyx_Buffer __pyx_pybuffer_j;
  double __pyx_r;
  __Pyx_RefNannyDeclarations
  __Pyx_RefNannySetupContext("Numeraire", 0);
  __pyx_pybuffer_j.pybuffer.buf = NULL;
  __pyx_pybuffer_j.refcount = 0;
  __pyx_pybuffernd_j.data = NULL;
  __pyx_pybuffernd_j.rcbuffer = &__pyx_pybuffer_j;
  {
    __Pyx_BufFmt_StackElem __pyx_stack[1];
    if (unlikely(__Pyx_GetBufferAndValidate(&__pyx_pybuffernd_j.rcbuffer->pybuffer, (PyObject*)__pyx_v_j, &__Pyx_TypeInfo_nn___pyx_t_5numpy_int_t, PyBUF_FORMAT| PyBUF_STRIDES, 1, 0, __pyx_stack) == -1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 9; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
  }
  __pyx_pybuffernd_j.diminfo[0].strides = __pyx_pybuffernd_j.rcbuffer->pybuffer.strides[0]; __pyx_pybuffernd_j.diminfo[0].shape = __pyx_pybuffernd_j.rcbuffer->pybuffer.shape[0];
/* … */
  /* function exit code */
  __pyx_L1_error:;
  { PyObject *__pyx_type, *__pyx_value, *__pyx_tb;
    __Pyx_ErrFetch(&__pyx_type, &__pyx_value, &__pyx_tb);
    __Pyx_SafeReleaseBuffer(&__pyx_pybuffernd_j.rcbuffer->pybuffer);
  __Pyx_ErrRestore(__pyx_type, __pyx_value, __pyx_tb);}
  __Pyx_WriteUnraisable("backward.Numeraire", __pyx_clineno, __pyx_lineno, __pyx_filename, 0);
  __pyx_r = 0;
  goto __pyx_L2;
  __pyx_L0:;
  __pyx_L2:;
  __Pyx_RefNannyFinishContext();
  return __pyx_r;
}

函数主体位于标记 /* … */ 的位中.

其中一些工作会在每次 Cython 调用时发生,但其中相当一部分与您未使用的 ndarray 相关,j (例如 __pyx_pybuffer_j__pyx_pybuffernd_j )

如果删除 j从参数列表来看,有函数调用和没有函数调用的速度是相同的。如果您确实需要,有多种选择 j对于该函数的非平凡、非示例版本。

  1. 如果您始终知道“j”的长度为 2,那么您可能会这样

    cdef double Numeraire(int i1, int i0, double j0, double j1):

  2. 或者,您可以将其传递为 C 风格 double* 、长度和可能的步幅(但如果你将 j 声明为 cdef ndarray[...,mode="c"] 你不需要它),这可能会更快。

  3. 最佳选择:最简单的选择是使用 new-style Cython typed memoryview interface而不是 ndarray界面。

代码:

cdef double Numeraire(int i1, int i0, long[::1] j):
  # code as before

# then within your calling function
  # ...
  cdef long[::1] j = np.empty(2, dtype=np.int)
  # ...

在这种情况下,这似乎几乎没有开销(但是,在其他一些情况下,我发现内存 View 接口(interface)速度稍慢(~1%),因此它并不总是最好的答案)。

关于performance - 为什么我的 Cython C 函数比它包装的内置函数慢 40 倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30471086/

相关文章:

node.js - MongoDB 填充性能

python - Ubuntu Cython 编译错误 : command 'x86_64-linux-gnu-gcc' failed with exit status 1

jupyter-notebook - 在 jupyter notebook 中使用 cython 进行线分析

python - Cython 内存 View : wrapping c function with array parameter to pass numpy array

python - Cython:具有自定义参数类型的 std::function 回调

python - 比较 startswith() .vs. 的速度在()

c++ - 为什么一个线程比调用一个函数更快,mingw

python - 如何在没有 Cython 的情况下在 Python 中运行 sklearn?

performance - 证明coldfusion函数在输出=true时不必要地缓冲

python - 从图像中查找车辆的速度