返回语句导致速度缓慢

3

我有这样的cython代码,只是用来测试:

cimport cython

cpdef loop(int k):
    return real_loop(k)

@cython.cdivision
cdef real_loop(int k):
    cdef int i
    cdef float a
    for i in xrange(k):
        a = i
        a = a**2 / (a + 1)
    return a

我用类似以下脚本的方式测试了此Cython代码与纯Python中相同代码之间的速度差异:

import mymodule

print(mymodule.loop(100000))

我得到了80倍的加速。但是如果我在Cython代码中删除两个返回语句,我会得到800-900倍的加速。为什么呢?

还有一件事,如果我在我的旧ACER Aspire ONE笔记本上运行此代码(带有返回),我会得到700倍的加速,在家里的新桌面i7 PC上会得到80倍的加速。

有人知道原因吗?


4
很难说——我在想编译器是否足够聪明,能否看出real_loop不会更新任何全局变量,并且它也不会改变任何参数,因此可以被转化为无操作。 - mgilson
1
你尝试过使用diff命令来比较这两个生成的C文件之间的差异吗?可能是因为Cython认为没有return语句的函数是无用的,并且根本没有执行这些函数。 - Bakuriu
1
@Bakuriu 是的,这就是我想的。可能根本不需要循环。昨天我尝试以不同的方式编写代码时,有时会得到荒谬的性能,比如快了 12,000 倍。 - Jean-Francois Gallant
4
那就太过了。这意味着编译器将整个循环替换为单个无操作指令。如果输入大小不变,那么如果所需时间没有改变,你应该会看到这一点。 - Bakuriu
2
你可能在这些系统上有不同的编译器。在我的Corei5笔记本电脑上,使用gcc4.7.2,mymodule.loop(N)始终需要大约120纳秒,无论N有多大,即它跳过了循环。 - dmytro
@Jean-FrancoisGallant,您尝试下面的答案了吗? - Saullo G. P. Castro
1个回答

0
我用以下的程式碼测试了你的问题:
#cython: wraparound=False
#cython: boundscheck=False
#cython: cdivision=True
#cython: nonecheck=False
#cython: profile=False

def loop(int k):
 return real_loop(k)

def loop2(int k):
 cdef float a
 real_loop2(k, &a)
 return a

def loop3(int k):
    real_loop3(k)
    return None

def loop4(int k):
    return real_loop4(k)

def loop5(int k):
 cdef float a
 real_loop5(k, &a)
 return a

cdef float real_loop(int k):
    cdef int i
    cdef float a
    a = 0.
    for i in range(k):
        a += a**2 / (a + 1)
    return a

cdef void real_loop2(int k, float *a):
    cdef int i
    a[0] = 0.
    for i in range(k):
        a[0] += a[0]**2 / (a[0] + 1)

cdef void real_loop3(int k):
    cdef int i
    cdef float a
    a = 0.
    for i in range(k):
        a += a**2 / (a + 1)

cdef float real_loop4(int k):
    cdef int i
    cdef float a
    a = 0.
    for i in range(k):
        a += a*a / (a + 1)
    return a

cdef void real_loop5(int k, float *a):
    cdef int i
    a[0] = 0.
    for i in range(k):
        a[0] += a[0]*a[0] / (a[0] + 1)

在你的函数附近,real_loop() 函数使用了一个修改后的公式来计算 a,因为原始公式看起来很奇怪。

real_loop2() 函数没有返回任何值,只是通过引用更新了 a

real_loop3() 函数也没有返回任何值。

检查生成的 C 代码中的 real_loop3(),可以看到循环已经存在,并且代码被调用……但我得出了与 @dmytro 相同的结论,改变 k 不会显著改变时间……所以这里肯定有我忽略的地方。

从下面的时间统计数据可以看出,return 不是瓶颈,因为 real_loop2()real_loop5() 都没有返回任何值,它们的性能与 real_loop()real_loop4() 相同。

In [2]: timeit _stack.loop(100000)
1000 loops, best of 3: 1.71 ms per loop

In [3]: timeit _stack.loop2(100000)
1000 loops, best of 3: 1.69 ms per loop

In [4]: timeit _stack.loop3(100000)
10000000 loops, best of 3: 78.5 ns per loop

In [5]: timeit _stack.loop4(100000)
1000 loops, best of 3: 913 µs per loop

In [6]: timeit _stack.loop5(100000)
1000 loops, best of 3: 979 µs per loop

请注意,将a**2更改为a*a会使速度提高约2倍,因为a**2在循环内需要调用powf()函数。

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