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

2024-05-23 23:00:42 发布

您现在位置:Python中文网/ 问答频道 /正文

我想测试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)

下面是第二个(仔细看,第一个函数定义很重要):

^{pr2}$

还有第三个,放在主文件里:

[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编译了它,得到了两个.sos。然后当我运行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函数,还是第一种混合类型的函数?在


Tags: 函数fromimportreturndefresultnum例子
1条回答
网友
1楼 · 发布于 2024-05-23 23:00:42

首先,您必须知道,在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和{}同样快。在

这可能会让python程序员感到意外,因为python解释器没有进行优化,因此此循环有O(n)-运行时间:

^{pr2}$

现在,调用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函数的调用。与通过整个python机制执行的def-version调用(这里有点缩写):

   ...
 __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 num创建一个python整数,以便能够调用python函数(__Pyx_PyInt_From_int
  2. 使用该方法的名称(__Pyx_GetModuleGlobalName+PyMethod_GET_SELF)定位该方法
  3. 最后调用函数。在

第一次调用可能至少快100倍,但总体速度低于2,这是因为调用“inner”函数并不是唯一需要完成的工作:def-函数xd和{}必须调用+必须创建结果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)

原因是integer pool表示值-5256=16^2,因此可以更快地构造此范围内的值。在


在您的示例中,指定返回类型并没有起到那么大的作用:它只决定到python integer的转换在哪里发生——要么在nocdef或{}中发生,但最终会发生。在

相关问题 更多 >