3个cython/python函数调用之间的差异
我想测试cython的性能,并将其与标准python进行比较。这里我有3个函数示例,它将循环通过200个整数,将相同的数字反复添加到结果中,然后返回结果。在3个cython/python函数调用之间的差异,python,cython,Python,Cython,我想测试cython的性能,并将其与标准python进行比较。这里我有3个函数示例,它将循环通过200个整数,将相同的数字反复添加到结果中,然后返回结果。在timeit模块中,我让它被调用1.000.000次 第一个例子是: [frynio@manjaro ctest]$ cat nocdefexample.pyx def nocdef(int num): cdef int result = 0 for i in range(num): result += nu
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 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编译了它,得到了两个。然后,当我运行python test.py
时,会显示:
[frynio@manjaro ctest]$ python test.py
0.10323301900007209
0.06339033499989455
11.448068103000423
因此,第一个是def(intnum)
。第二个(似乎比第一个快1.5倍)是cdefint(int num)
。最后一个是def(num)
最后一场表演很糟糕,但这正是我想看的。对我来说有趣的是为什么前两个例子不同(我检查了很多次,第二个总是比第一个快~1.5x
)
是否只是因为我指定了返回类型
如果是这样,这是否意味着它们都是cython
函数,或者是第一个某种类型的,我不知道,混合类型的函数?首先,你必须知道,在cython函数的情况下,你只是测量调用cdef
-vs.adef
-函数的开销:
>>> %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解释器没有优化,因此这个循环有一个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的创建):
\uuuupyx\uf\u4test\ucdefex
-只是对C函数的调用。与通过整个python机制调用def
-版本相比(这里有点缩写):
赛昂人必须:
从C-intnum
创建一个python整数,以便能够调用python函数(\uuuupyx\upyint\ufrom\uint
)
使用此方法的名称(\uuupyx\uGetModuleGlobalName
+PyMethod\uGet\uSelf
)定位此方法
最后调用函数
第一次调用可能至少快100倍,但总体速度小于2,这只是因为调用“内部”函数不是唯一需要完成的工作:def
-无论如何都必须调用函数xd
和xd1
,并且必须创建生成的python整数
有趣的事实:
>>> %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)
原因是值-5
..256
=16^2
的错误,因此可以更快地构造此范围内的值
在您的示例中,指定返回类型并没有起到那么大的作用:它只决定转换为python整数的位置—在nocdef
或xd1
中—但最终会发生。您能否详细说明一下,本页中有什么您不清楚的?调用def…(num)的区别
和def…(int-num)
。为什么第二个更快。为什么它比cdef int…(int num)
我链接到的页面解释了这一点。我不知道如果一个人不知道你不理解的是什么,怎么才能更好地解释它。您提到的两种方法之间的差异显然是类型提示,这显然是性能差异的原因。另外两种方法的区别显然是函数本身的声明,并且在我链接的页面上(向我)清楚地解释了/that/behavior。我认为更合理的测试应该是直接调用nocdef
。实际上有两层Python调用开销(例如,xd
检查参数是否为int
,然后将其传递给nocdef
,后者检查参数是否为int
)。更合理的测试是使用num**2
;)正是我想要的
__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_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)
>>> %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)