gpt4 book ai didi

python - 3 个 cython/python 函数调用之间的差异

转载 作者:太空宇宙 更新时间:2023-11-03 14:06:25 24 4
gpt4 key购买 nike

我想测试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 += 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 s。然后当我运行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函数还是第一个,我不知道,混合类型的函数?

最佳答案

首先,您必须意识到,在 cython 函数的情况下,您仅测量调用 cdef 的开销- 与a 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 解释器不会优化,因此这个循环有一个 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 机器发生的版本(这里有点缩写):

   ...
__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 必须:

  1. 从 C-int 创建一个 python-integer num能够调用 Python 函数 ( __Pyx_PyInt_From_int )
  2. 使用名称找到此方法 ( __Pyx_GetModuleGlobalName + PyMethod_GET_SELF )
  3. 最后调用该函数。

第一次调用可能至少快了 100 倍,但总体加速率小于 2,只是因为调用“内部”函数并不是唯一需要完成的工作:def -功能xdxd1无论如何都必须调用+必须创建生成的python-integer。

有趣的事实:

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

原因是integer pool对于值 -5 ... 256 =16^2因此可以更快地构建此范围内的值。

<小时/>

指定返回类型在您的示例中并没有发挥那么大的作用:它只决定发生到 python-integer 的转换的位置 - 或者在 nocdef 中或xd1 - 但它最终会发生。

关于python - 3 个 cython/python 函数调用之间的差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48854878/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com