为什么Python和Cython中这两个代码之间存在巨大的性能差异?

时间:2014-04-08 12:01:46

标签: python performance cython

我在Python中遇到了性能问题,我的一位朋友建议我使用Cython 搜索更多后,我发现了here

中的代码

的Python:

def test(value):
    for i in xrange(value):
        z = i**2
        if(i==1000000):
            print i
        if z < i:
                print "yes"
test(10000001)

用Cython:

def test(long long value):
    cdef long long i
    cdef long long z
    for i in xrange(value):
        z = i**2
        if(i==1000000):
            print i
        if z < i:
            print "yes"

test(10000001)

在我执行两个代码后,令人惊讶的是我通过Cython实现了100倍的加速

为什么只是通过添加变量声明来实现这种加速? 另外我应该提到波纹管代码的性能与Cython中的Python相同。

用Cython:

def test(long long value):
    for i in xrange(value):
        z = i**2
        if(i==1000000):
            print i
        if z < i:
            print "yes"

test(10000001)

2 个答案:

答案 0 :(得分:6)

Python是一种语言。 CPython是Python的字节码编译器解释器

需要一些代码:

for i in xrange(value):
    z = i**2
    if(i==1000000):
        print i
    if z < i:
        print "yes"

并给你“字节码”:

  • 将迭代器加载到for循环中,并将其内容循环到i
  • 加载i,加载2,运行二进制电源,存储z
  • 加载i,加载1000000,比较
  • 加载i,打印
  • 加载z,加载i,比较
  • 加载'yes',打印
  • 光洁度

完整:

  1           0 SETUP_LOOP              70 (to 73)
              3 LOAD_NAME                0 (xrange)
              6 LOAD_NAME                1 (value)
              9 CALL_FUNCTION            1
             12 GET_ITER            
        >>   13 FOR_ITER                56 (to 72)
             16 STORE_NAME               2 (i)

  2          19 LOAD_NAME                2 (i)
             22 LOAD_CONST               0 (2)
             25 BINARY_POWER        
             26 STORE_NAME               3 (z)

  3          29 LOAD_NAME                2 (i)
             32 LOAD_CONST               1 (1000000)
             35 COMPARE_OP               2 (==)
             38 POP_JUMP_IF_FALSE       49

  4          41 LOAD_NAME                2 (i)
             44 PRINT_ITEM          
             45 PRINT_NEWLINE       
             46 JUMP_FORWARD             0 (to 49)

  5     >>   49 LOAD_NAME                3 (z)
             52 LOAD_NAME                2 (i)
             55 COMPARE_OP               0 (<)
             58 POP_JUMP_IF_FALSE       13

  6          61 LOAD_CONST               2 ('yes')
             64 PRINT_ITEM          
             65 PRINT_NEWLINE       
             66 JUMP_ABSOLUTE           13
             69 JUMP_ABSOLUTE           13

        >>   72 POP_BLOCK           
        >>   73 LOAD_CONST               3 (None)
             76 RETURN_VALUE

值得注意的是,在Python中,整数是类intlong实例。这意味着不仅有数字,还有指针和另一条信息,说明它至少是的类。这会产生很多开销。

但值得注意xrange如何运作。

xrange创建一个可由LOAD_NAME (xrange)迭代的类实例(CALL_FUNCTIONfor)。 for将(基本上)委托给迭代器__iter__上的函数调用。每个循环都有一个函数调用

此外,每次要获取或设置变量zi时,都必须查看本地词典。这真的很慢。


在Cython中运行纯Python代码:

当您在Cython中运行它时(问题中的第三个示例),它会编译为C.但是所有这些C都是告诉 CPython虚拟机要做什么。

单独的CPython:一个人从书中读书,并且实际上履行其职能 CPython与Cython:一个人大喊指示 那个能够实现其功能的人。

它可能会快一点,但缓慢的部分仍然是CPython正在慢慢地开始工作。


使用 cythonized 代码:

当你cdef long long时会发生什么?

  • Cython知道xrange正在对long long

    采取行动
    • 它知道循环是有效的(所以它不必检查你是否给它list或某些东西)

    • 它知道循环不会溢出(因为它确实是未定义的!)

    • 因此可以将其变成C循环(for (int index=0; index<copy_of_value; index++) { i = index; ... }

  • 这可以避免intlong类,它们具有很多的间接开销和类型检查

  • 这可以避免字典查找。事情始终是你把它们放在堆栈上的地方

  • 例如i ** 2更简单,因为例程可以内联(它总是一个数字,粗鲁)并直接处理整数并忽略溢出

因此,结果最终主要由C运行,并且仅针对某些清理内容和print调用进入CPython。


有意义吗?

答案 1 :(得分:1)

正如我在评论中提到的:你的第三个解决方案是较慢/ as-slow-as-python-version,因为它缺少允许Cython加速代码的静态类型功能。当你将一个变量声明为long f.e。时,Cython不需要构建一个昂贵的&#34; Python-Object,但可能完全依赖于C-Code。我不是Cython也不是Python专家,但我认为Python的对象构建是主要的瓶颈。