如何在Cython中编写生成器函数?

15
如果我有一个Python生成器函数,比如说:
def gen(x):
    for i in range(x):
        yield(i ** 2)
我该如何在Cython中声明输出数据类型为int?这样做是否值得?
谢谢。
编辑:我看到在变更日志中提到了(async)生成器的实现:http://cython.readthedocs.io/en/latest/src/changes.html?highlight=generators#id23
然而,没有关于如何使用它们的文档。是因为它们受支持,但在Cython中使用它们没有特别的优势或者没有优化可能吗?
1个回答

17

不,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;

最重要的部分是 body 成员:这个函数执行实际的计算。正如我们所看到的,它返回一个 PyObject,目前没有办法将其适配为 intdouble 或类似类型。

至于为什么不这样做,我只能猜测——但可能有不止一个原因。

如果你真的关心性能,那么生成器无论如何都会引入太多的开销(例如,在 cdef 函数中不可能使用 yield),应该将其重构为更简单的形式。


更详细地阐述可能的重构方案。让我们假设我们想要对所有创建的值进行求和:

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

好消息是,它比纯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

它是:

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

大约快了50倍。

我明白,这是一个相当大的变化,可能很难做到 - 我只会在真正成为程序瓶颈时才这样做 - 但是加速50倍确实是一个真正的动力。

显然还有很多其他方法:使用numpy数组或array.array代替生成器,或者编写自定义生成器(cdef-class),它将提供另一种获取int-值而不是PyObjects的快速/高效方式 - 但所有这些都取决于你手头的情况。我只想说明可以通过放弃生成器来提高性能。


1
谢谢你的回答。我原本以为生成器通常更有效率,至少在内存方面是这样的。因此,如果我要重构我的函数来返回一个集合或列表,我应该声明哪种返回类型? - user3758232
2
@user3758232,我稍微详细解释了一下我所说的“重构”的含义。如果你退回到返回整个数据,我会选择array.array或numpy数组,因为它们存储的不是Python对象,而是原始的int/double等数据类型 - 需要更少的内存并且速度更快。 - ead
非常非常有帮助。谢谢。实际上,我已经有一个内部函数,可以按照您建议的方式进行优化,并且还有一个外部循环,可能会被多次调用。为此,我可以研究一下数组。 - user3758232

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