Cython内存视图作为返回值

15

考虑这个虚拟的Cython代码:

#!python
#cython: boundscheck=False
#cython: wraparound=False
#cython: initializedcheck=False
#cython: cdivision=True
#cython: nonecheck=False

import numpy as np

# iterator function
cdef double[:] f(double[:] data):
    data[0] *= 1.01
    data[1] *= 1.02
    return data

# looping function
cdef double[:] _call_me(int bignumber, double[:] data):
    cdef int ii
    for ii in range(bignumber):
        data = f(data)
    return data

# helper function to allow calls from Python
def call_me(bignumber):
    cdef double[:] data = np.ones(2)
    return _call_me(bignumber, data)

现在,如果我对此运行cython -a,它会用黄色显示返回语句。我正在一个非常关键性能的程序中做类似的事情,根据分析结果,这确实会减慢我的代码速度。那么,为什么cython需要这些return语句的python呢?注释文件给了一个提示:

PyErr_SetString(PyExc_TypeError,"Memoryview return value is not initialized");

令人惊讶的是,通过谷歌搜索cython "Memoryview return value is not initialized",没有任何结果。


Cython 版本 0.19.2 - HenriV
在你的实际代码中,你需要返回memoryview还是可以像这里一样就地修改它?这样做可以让我获得40倍的加速。我不确定是否有一种方法可以关闭这个检查... - jorgeca
真正的代码是迭代求解常微分方程,所以是的,我需要返回它。 - HenriV
嗯,让我们看看Cython专家是否知道一种快速返回小内存视图的方法。作为解决方法,f可以重写为接受data_in和data_out缓冲区而不是返回它。 - jorgeca
1个回答

7
缓慢的部分并不是你想象中的那样。缓慢的部分主要是(嗯...)
data = f(data)

不是 f(data),而是 data =
这将赋值一个被定义为如下的struct
typedef struct {
  struct __pyx_memoryview_obj *memview;
  char *data;
  Py_ssize_t shape[8];
  Py_ssize_t strides[8];
  Py_ssize_t suboffsets[8];
} __Pyx_memviewslice;

而提到的任务确实

__pyx_t_3 = __pyx_f_3cyt_f(__pyx_v_data);

其中__pyx_t_3是该类型。如果像现在这样在循环中频繁执行此操作,复制结构体所需的时间比执行函数的微不足道的主体要长得多。我用纯C进行了计时,结果差不多。

(编辑说明:分配实际上主要是个问题,因为它还会导致结构体和其他副本的生成不能被优化掉。)

然而,整件事看起来很愚蠢。复制结构体的唯一原因是如果有什么变化,但其实没有。内存指向同一个位置,数据指向同一个位置,形状、步幅和偏移量也相同。

我唯一能想到避免struct复制的方法是不更改它引用的任何内容(即始终返回给定的memoryview)。这只在无论如何返回都毫无意义的情况下才可能发生,就像这里一样。或者你可以像我一样对C进行修改。只要你不破坏任何东西就行,不过别哭。


另请注意,您可以使函数成为nogil,因此与追溯到Python无关。


编辑

C的优化编译器稍微让我有些困惑。基本上,我删除了一些赋值,它就删除了很多其他东西。基本上慢的路径是这样的:

#include<stdio.h>


struct __pyx_memoryview_obj;


typedef struct {
  struct __pyx_memoryview_obj *memview;
  char *data;
  ssize_t shape[8];
  ssize_t strides[8];
  ssize_t suboffsets[8];
} __Pyx_memviewslice;


static __Pyx_memviewslice __pyx_f_3cyt_f(__Pyx_memviewslice __pyx_v_data) {
  __Pyx_memviewslice __pyx_r = { 0, 0, { 0 }, { 0 }, { 0 } };
  __pyx_r = __pyx_v_data;
  return __pyx_r;
}

main() {
    int i;
    __Pyx_memviewslice __pyx_v_data = {0, 0, { 0 }, { 0 }, { 0 }};

    for (i=0; i<10000000; i++) {
        __pyx_v_data = __pyx_f_3cyt_f(__pyx_v_data);
    }
}

(没有进行编译优化)我不是C程序员,如果我复制计算机生成的代码的方式有些问题与事实不直接相关,请谅解。
我知道这并不能“帮助”你,但我已经尽力了,好吧?

6
感谢你表明这个问题比我想象的更为复杂,并感谢给出 "nogil" 小提示。 - HenriV

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接