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

时间:2018-05-16 05:07:37

标签: python cython

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

def gen(x):
    for i in range(x):
        yield(i ** 2)

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

感谢。

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

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

1 个答案:

答案 0 :(得分:8)

不,在Cython中无法做到这一点。

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

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;

最重要的部分是body - 成员:这是执行实际计算的函数。正如我们所看到的那样,它会返回PyObject并且无法(但是?)将其调整为intdouble或类似内容。

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

如果您真的关心性能,那么生成器无论如何都会引入太多开销(例如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 - 并且应该重构为更简单的东西。

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

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

时间安排导致:

%%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

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

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

是:

array.array

快了约50倍。

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

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