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

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

我想测试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 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.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的创建):

\uuuupyx\uf\u4test\ucdefex
-只是对C函数的调用。与通过整个python机制调用
def
-版本相比(这里有点缩写):

赛昂人必须:

  • 从C-int
    num
    创建一个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)