【问题标题】:How to avoid PyObject_to_MemoryviewSlice, GOTREF / DECREF Python API calls?如何避免 PyObject_to_MemoryviewSlice、GOTREF / DECREF Python API 调用?
【发布时间】:2016-05-25 15:21:10
【问题描述】:

我在对我的代码进行 cythonizing 时遇到问题,更具体地说是以下(和类似的)片段:

cdef double [:,:] grad_d_him_d_jm
grad_d_ihm_d_jm = grad_d_im_d_jm(...)

其中 grad_d_im_d_jm(...) 将返回一个双 [:,:] 内存视图。 此代码将由 Cython 翻译成以下 C 代码:

__pyx_t_1 = __pyx_f_24gradient_better_c_mviews_grad_d_im_d_jm(__pyx_v_i, __pyx_v_j, __pyx_v_m, __pyx_v_structure, __pyx_v_distances); 
if (unlikely(!__pyx_t_1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 203;  __pyx_clineno = __LINE__; goto __pyx_L1_error;}
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_7 = __Pyx_PyObject_to_MemoryviewSlice_dsds_double(__pyx_t_1);
if (unlikely(!__pyx_t_7.memview)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 203; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__pyx_v_grad_d_ihm_d_jm = __pyx_t_7;
__pyx_t_7.memview = NULL;
__pyx_t_7.data = NULL;

当我在循环中执行此操作时,我怀疑 Python API 调用会对我的代码速度产生相当大的影响。

GOTREF / DECREF 调用也发生在其他场合,以及 PyFloat_asFloat:

cdef float sp
sp = scalar_product()

其中 scalar_product() 返回一个 cdef 浮点数。这个 sn-p 被翻译成

__pyx_t_1 = __pyx_f_24gradient_better_c_mviews_scalar_product(__pyx_v_i, __pyx_v_j, __pyx_v_m, __pyx_v_structure); 
if (unlikely(!__pyx_t_1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 178; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
__Pyx_GOTREF(__pyx_t_1);
__pyx_t_2 = __pyx_PyFloat_AsFloat(__pyx_t_1); 
if (unlikely((__pyx_t_2 == (float)-1) && PyErr_Occurred())) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 178; __pyx_clineno = __LINE__; goto __pyx_L1_error;}
__Pyx_DECREF(__pyx_t_1); __pyx_t_1 = 0;
__pyx_v_sp = __pyx_t_2;

我正在运行 Python 2.7.11+ 和 Cython 0.23.4。如果您能告诉我 a) 这与性能无关或 b) 如何解决它,我将不胜感激。 如果我能改进这个问题,请告诉我,我很乐意这样做。

【问题讨论】:

    标签: python cython memoryview


    【解决方案1】:

    这些似乎是 Cython 引用计数 API 的一部分,已解释 here

    我的猜测是grad_d_im_d_jm 返回一个 Python 对象(例如 NumPy 数组),因此 Cython 必须在获取 memoryview 后递减对象引用计数器。

    至于scalar_product,我认为它要么是def(而不是cdef)要么是无类型的。比如下面的

    cdef g():
        return 1.0
    

    编译成

    // ...
    __Pyx_XDECREF(__pyx_r);
    __Pyx_INCREF(__pyx_float_1_0);
    __pyx_r = __pyx_float_1_0;
    goto __pyx_L0;
    

    但是,一旦你指定了返回类型,引用计数调用就消失了

    cdef float g():
        return 1.0
    

    变成

    // ...
    __pyx_r = 1.0;
    goto __pyx_L0;
    

    【讨论】:

    • 感谢您的回答! scalar_product 是一个 cdef,它的返回值是一个 cdef 的浮点数。 grad_d_im_d_jm 也是如此;只是返回的值是一个 cdef 的浮点型 2D 内存视图。实际上,当打印 type(grad_d_im_d_jm) 时,它说它是一个 memoryviewslice。我在 grad_d_im_d_jm() 中所做的是使用 mv=numpy.zeros((n,n)) 初始化内存视图,然后将内容添加到单个元素。我调查了 refnanny 的事情,但还没有找到关闭它的方法(仅作为 runtests.py 的一个选项)。你会碰巧认识一个吗?
    • Nanny 仅用于调试 Cython 本身。例如。生成的 C 文件有一个 ifdef 将 __Pyx_DECREF 替换为 Py_DECREF,除非用户专门设置了 -DCYTHON_REFNANY
    • 可以分享相关代码吗?我无法使用存根函数在本地重现该问题。
    • 我觉得是时候让我尴尬了。到目前为止,我主要将单个函数 cythonized 为 (cp)defs,但在我的代码中,我调用了很多 cdef 函数。我其实没有想到在函数头中指定返回类型!我没有仔细阅读您的答案,否则我会立即注意到。很抱歉!不过,有趣的是,指定返回类型只会导致大约 15% 的加速。无论如何,非常感谢!你的回答当然是正确的——我只是想我在函数体中使用 cdef 指定返回的变量。
    猜你喜欢
    • 2014-11-24
    • 1970-01-01
    • 1970-01-01
    • 2021-10-04
    • 2018-07-18
    • 2021-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多