我想测试cython
性能,将其与标准python进行比较。所以这里我有3个函数的例子,它将遍历200个int,一遍又一遍地向结果添加相同的数字,然后返回结果。在timeit
模块中,我将其称为1.000.000
次。
所以有第一个例子:
[frynio@manjaro ctest]$ cat nocdefexample.pyx
def nocdef(int num):
cdef int result = 0
for i in range(num):
result += num
return result
def xd(int num):
return nocdef(num)
这是第二个(仔细看,第一个函数定义很重要):
[frynio@manjaro ctest]$ cat cdefexample.pyx
cdef int cdefex(int num):
cdef int result = 0
for i in range(num):
result += num
return result
def xd1(int num):
return cdefex(num)
还有第三个,放在主文件中:
[frynio@manjaro ctest]$ cat test.py
from nocdefexample import xd
from cdefexample import xd1
import timeit
def standardpython(num):
result = 0
for i in range(num):
result += num
return result
def xd2(num):
return standardpython(num)
print(timeit.timeit('xd(200)', setup='from nocdefexample import xd', number=1000000))
print(timeit.timeit('xd1(200)', setup='from cdefexample import xd1', number=1000000))
print(timeit.timeit('xd2(200)', setup='from __main__ import xd2', number=1000000))
我用cythonize -a -i nocdefexample.pyx cdefexample.pyx
编译了它,我得到了两个.so
。然后当我运行python test.py
时 - 显示:
[frynio@manjaro ctest]$ python test.py
0.10323301900007209
0.06339033499989455
11.448068103000423
所以第一个只有def <name>(int num)
。第二个(似乎比第一个快1.5x
)是cdef int <name>(int num)
。最后一个只是def <name>(num)
。
最后一次演出很糟糕,但这就是我想看到的。对我来说有趣的是为什么前两个例子不同(我多次检查,第二个总是比第一个快{〜1.5x
。)
是否只是因为我指定了返回类型?
如果是这样,是否意味着它们都是cython
函数或者是第一种,我不知道,混合型函数?
答案 0 :(得分:1)
首先,您必须意识到,在cython函数的情况下,您只需要测量调用cdef
- 与def
- 函数的开销:
>>> %timeit nocdef(1000)
60.5 ns ± 0.73 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit nocdef(10000)
60.1 ns ± 1.2 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
C编译器识别出循环将导致num*num
并直接评估此乘法而不运行循环 - 并且10**3
和10**4
的乘法同样快。
对于python程序员来说,这可能会让人感到意外,因为python-interpreter没有优化,因此这个循环有一个O(n)
- 运行时间:
>>> %timeit standardpython(1000)
43.7 µs ± 182 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit standardpython(10000)
479 µs ± 4.95 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
现在,调用cdef
函数要快得多!只需查看生成的用于调用cdef
版本的C代码(实际上已经包含了python-integer的创建):
__pyx_t_1 = __Pyx_PyInt_From_int(__pyx_f_4test_cdefex(__pyx_v_num)); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 19, __pyx_L1_error)
__pyx_f_4test_cdefex
- 只是一个C函数的调用。与def
的调用相比 - 通过整个python-machinery(这里有一个缩写)发生的版本:
...
__pyx_t_2 = __Pyx_GetModuleGlobalName(__pyx_n_s_nocdef); if (unlikely(!__pyx_t_2)) __PYX_ERR(0, 9, __pyx_L1_error)
...
__pyx_t_3 = __Pyx_PyInt_From_int(__pyx_v_num); if (unlikely(!__pyx_t_3)) __PYX_ERR(0, 9, __pyx_L1_error)
...
__pyx_t_4 = PyMethod_GET_SELF(__pyx_t_2);
...
__pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_t_2, __pyx_t_3); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 9, __pyx_L1_error)
Cython必须:
num
创建一个python-integer,以便能够调用Python函数(__Pyx_PyInt_From_int
)__Pyx_GetModuleGlobalName
+ PyMethod_GET_SELF
)第一个呼叫可能至少快100倍,但总体速度提升小于2只是因为调用&#34;内部功能并不是唯一需要完成的工作:{ {1}} - 无论如何都必须调用函数def
和xd
+必须创建生成的python-integer。
逢事实:
xd1
原因是值 >>> %timeit nocdef(16)
44.1 ns ± 0.294 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit nocdef(17)
58.5 ns ± 0.638 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
的{{3}} ... -5
= 256
因此可以更快地构建此范围内的值。
指定返回类型并不会在您的示例中发挥重要作用:它只决定在16^2
或nocdef
中转换为python-integer的位置 - 但它会发生最终