如何在Cython中键入生成器函数?

use*_*232 9 python cython

如果我在Python中有一个生成器函数,请说:

def gen(x):
    for i in range(x):
        yield(i ** 2)
Run Code Online (Sandbox Code Playgroud)

如何声明输出数据类型是否int在Cython中?它是否值得一试?

谢谢.

编辑:我读过在更改日志中实现的(异步)生成器的提及:http://cython.readthedocs.io/en/latest/src/changes.html? highlight = generator#id23

但是没有关于如何使用它们的文档.是因为它们受到支持,但使用Cython或没有可能的优化没有特别的优势吗?

ead*_*ead 9

不,在Cython中没有办法做到这一点.

当您查看Cython生成的代码时,您将看到gen(和其他生成器函数)返回一个生成器,它基本上是一个__pyx_CoroutineObject对象,如下所示:

typedef PyObject *(*__pyx_coroutine_body_t)(PyObject *, PyThreadState *, PyObject *);
typedef struct {
    PyObject_HEAD
    __pyx_coroutine_body_t body;
    PyObject *closure;
    ...
    int resume_label;
    char is_running;
} __pyx_CoroutineObject;
Run Code Online (Sandbox Code Playgroud)

最重要的部分是body-member:这是进行实际计算的函数.正如我们可以看到它返回一个PyObject,也没有为它适应的方式(没有?) int,double或类似的.

至于为什么没有这样做的原因,我只能推测 - 但可能不止一个原因.

如果你真的关心性能,那么生成器无论如何都会引入太多的开销(例如,yield在cdef函数中是不可能的),并且应该重构为更简单的东西.


详细说明可能的重构.作为基线,我们假设我们想要总结所有创建的值:

%%cython 
def gen(int x):
    cdef int i
    for i in range(x):
        yield(i ** 2)

def sum_it(int n):
    cdef int i
    cdef int res=0
    for i in gen(n):
        res+=i
    return res
Run Code Online (Sandbox Code Playgroud)

时间安排导致:

>>> %timeit sum_it(1000)
28.9 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Run Code Online (Sandbox Code Playgroud)

好消息:它比纯python版快10倍,但如果我们真的在速度之后:

%%cython 
cdef int gen_fast(int i):
    return i ** 2

def sum_it_fast(int n):
    cdef int i
    cdef int res=0
    for i in range(n):
        res+=gen_fast(i)
    return res
Run Code Online (Sandbox Code Playgroud)

它是:

>>> %timeit sum_it_fast(1000)
661 ns ± 20.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Run Code Online (Sandbox Code Playgroud)

大约快50倍.

我明白,这是一个相当大的改变,可能很难做到 - 只有当它真的是我的计划的瓶颈时才会这样做 - 但是加速50将是一个真正的动力去做.

显然还有很多其他方法:使用numpy-arrays或array.array代替生成器或编写自定义生成器(cdef-class),这将提供额外的快速/有效的可能性来获取int-values而不是PyObjects- 但这一切都取决于你的手头的情景.我只想表明有可能通过抛弃发电机来提高性能.

  • @user3758232 我详细阐述了“重构”的含义。如果你返回整个数据,我会选择 array.array 或 numpy 数组,因为它们存储的不是 Python 对象,而是原始整数/双精度数等 - 所需内存更少且速度更快。 (2认同)