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

时间:2018-02-18 18:00:22

标签: python cython

我想测试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函数或者是第一种,我不知道,混合型函数?

1 个答案:

答案 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**310**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必须:

  1. 从C-int num创建一个python-integer,以便能够调用Python函数(__Pyx_PyInt_From_int
  2. 使用其名称(__Pyx_GetModuleGlobalName + PyMethod_GET_SELF
  3. 找到此方法
  4. 最后调用该函数。
  5. 第一个呼叫可能至少快100倍,但总体速度提升小于2只是因为调用&#34;内部功能并不是唯一需要完成的工作:{ {1}} - 无论如何都必须调用函数defxd +必须创建生成的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^2nocdef中转换为python-integer的位置 - 但它会发生最终