簡體   English   中英

為什么Python和Cython中這兩個代碼之間存在巨大的性能差異?

[英]Why there is a huge performance difference between these two codes in Python and Cython?

我遇到了在Python的性能問題,我的一個朋友建議我用用Cython搜索更長時間后,我發現這個代碼在這里

蟒蛇:

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)

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創建了一個可以由for迭代的類實例( LOAD_NAME (xrange)CALL_FUNCTION )。 for (基本上)將委托給迭代器的__iter__上的函數調用。 每個循環都有一個函數調用。

此外,每次要獲取或設置變量zi ,都必須查看本地字典。 這真的很慢。


在Cython中運行純Python代碼:

當你在Cython中運行它(問題中的第三個例子)時,它會編譯為C.但是所有這些C的作用都是告訴 CPython虛擬機要做什么。

僅CPython:一個人從書中讀書,並且實際執行其功能。
CPython的與用Cython:一個人 說明誰merticulously實現其功能的人。

它可能會快一點,但緩慢的部分仍然是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運行,並且只進入CPython進行一些清理和print調用。


合理?

正如我在評論中提到的:你的第三個解決方案是較慢/ as-slow-as-python-version,因為它缺少允許Cython加速代碼的靜態類型功能。 當你將變量聲明為long fe時,Cython不需要構造一個“昂貴的”Python-Object,但可能完全依賴於C-Code。 我不是Cython也不是Python專家,但我猜Python的對象構造是主要的瓶頸。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM