如果我在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或没有可能的优化没有特别的优势吗?
答案 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
并且无法(但是?)将其调整为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
- 并且应该重构为更简单的东西。
详细说明可能的重构。作为基线,我们假设我们想要总结所有创建的值:
>>> %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
- 但这完全取决于您手头的情况。我只想表明有可能通过抛弃发电机来提高性能。