2012-09-25 61 views
32

我想我可能已經實現了這個不正確,因爲結果沒有意義。我有一個去計劃1000000000可以比python真的快嗎?

package main 

    import (
     "fmt" 
    ) 

    func main() { 
     for i := 0; i < 1000000000; i++ {} 
     fmt.Println("Done") 
    } 

它完成不到一秒鐘。另一方面,我有一個python腳本

x = 0 
    while x < 1000000000: 
     x+=1 
    print 'Done' 

它在幾分鐘內完成。

爲什麼Go版本要快得多。他們都在計數高達10億或者我錯過了什麼?

回答

66

十億是不是一個很大的數字。任何合理的現代化機器應該最多能夠在幾秒鐘內完成此操作,如果能夠使用本機類型完成這項工作,則需要。我通過編寫一個等效的C程序來驗證這一點,閱讀程序集以確保它實際上正在添加,並對其進行計時(它在我的計算機上大約在1.8秒內完成)。

然而,Python沒有原生類型變量的概念(或者有意義的類型註釋),所以在這種情況下它必須做好幾百次的工作。總之,對你的標題問題的答案是「是」。真的可以可以比Python快得多,即使沒有任何類型的編譯器技巧,比如優化一個無副作用的循環。

-1

我不熟悉去,但我猜想去版本忽略循環,因爲循環的主體什麼都不做。另一方面,在python版本中,你正在循環體內增加x,所以它可能實際上正在執行循環。

+0

我改變了for循環,爲每個循環分配i給另一個變量(即i2 = i),速度仍然相同(所以基本上我知道for循環被執行)。 – bab

+0

我在程序結束時打印了i2,i2是999999999 – bab

15

這種情況下高度支持體面的本地編譯靜態類型語言。本地編譯的靜態類型語言能夠發出非常簡單的循環,即4-6個CPU操作碼,它利用簡單的檢查條件進行終止。這個循環有有效分支預測失誤,並能有效地認爲是執行遞增每個CPU週期(這是不完全正確,但..)

Python實現必須做顯著更多的工作,主要是由於動態分類。 Python必須進行幾個不同的調用(內部和外部),才能將兩個int一起添加。在Python中,它必須調用__add__(它實際上是i = i.__add__(1),但該語法只能在Python 3.x中工作),而這又必須檢查傳遞值的類型(以確保它是一個int),然後它會添加整數值(從對象中提取它們),然後新的整數值再次包含在新對象中。最後,它將新對象重新分配給局部變量。這是顯着更多的工作比單個操作碼增加,甚至沒有處理循環本身 - 相比之下,Go /本地版本可能只是通過副作用遞增寄存器。

Java將會公平很多在這樣一個微不足道的基準中會更好,並且可能會相當接近Go;靜態鍵入的計數器變量JIT和可以保證這個(它使用特殊的整數加JVM指令)。 Python再次沒有這樣的優勢。現在,有一些實現類似PyPy/RPython,它們運行的​​是靜態輸入階段,應該比CPython好得多。

+6

我不是故意用這個作爲基準(對不起,如果我沒有說清楚)。我只是想知道爲什麼python版本慢得多。 – bab

+0

-1:你最後的「天生具有誤導性」的評論似乎是沒有理由或解釋的獨立聲明。 – igouy

+1

@igouy我不明白這是如何沒有根據(整個職位是一個理由),但我刪除它,因爲它沒有增加任何新東西。 – 2012-09-27 17:50:25

-1

有可能編譯器意識到你沒有使用「i」變量循環,所以它通過刪除循環來優化最終代碼。

即使你使用它之後,編譯器可能是足夠聰明與

i = 1000000000; 

希望這有助於=)

+0

您可以通過獲取彙編程序列表來檢查循環是否仍在代碼中:'go build -gcflags -S main.go' – topskip

8

你在工作中有兩件事情在這裏代替循環。第一個是Go被編譯爲機器代碼並直接在CPU上運行,而Python被編譯爲針對(特別慢)VM運行的字節碼。

影響性能的第二個也是更重要的事情是這兩個程序的語義實際上有很大的不同。 Go版本會創建一個名爲「x」的「盒子」,其中包含一個數字,並在每次通過該程序時遞增1。 Python版本實際上必須在每個循環中創建一個新的「盒子」(int對象)(並且最終必須將它們扔掉)。我們可以通過稍微修改你的程序證明這一點:

package main 

import (
    "fmt" 
) 

func main() { 
    for i := 0; i < 10; i++ { 
     fmt.Printf("%d %p\n", i, &i) 
    } 
} 

...和:

x = 0; 
while x < 10: 
    x += 1 
    print x, id(x) 

這是因爲圍棋,由於它的Ç根,以一個變量名來引用地方,其中Python採用變量名稱來指代事物。由於整數被認爲是python中唯一的,不可變的實體,我們必須不斷地創建新的實體。 Python應該比Go慢,但是你選擇了最壞的情況 - in the Benchmarks Game,我們看到的平均速度大約快25倍(在最壞的情況下是100倍)。

你可能讀到過,如果你的Python程序太慢了,你可以通過將東西移動到C來加速它。幸運的是,在這種情況下,有人已經爲你做了這個。如果你重寫你的空循環使用xrange()像這樣:

for x in xrange(1000000000): 
    pass 
print "Done." 

...你會看到它運行約快一倍。如果你發現循環計數器實際上是你的程序中的一個主要瓶頸,那麼可能是研究解決問題的新方法的時候了。

+1

更好地使用Python和Go-http:// shootout之間的直接基準遊戲比較。 alioth.debian.org/u64q/benchmark.php?test=all&lang=go&lang2=python3 - 您似乎已經感到困惑並報告了Python的性能與C的對比情況。 – igouy

50

pypy實際上沒有加快這一循環

def main(): 
    x = 0 
    while x < 1000000000: 
     x+=1 

if __name__ == "__main__": 
    s=time.time() 
    main() 
    print time.time() - s 

$ python count.py 
44.221405983 
$ pypy count.py 
1.03511095047 

〜97%的增速令人印象深刻的工作!

澄清3人誰沒有「得到它」。Python語言本身並不慢。 CPython實現是運行代碼的一種相對直接的方式。 Pypy是語言的另一種實現,它執行許多棘手的問題(尤其是JIT),這些問題可能會造成巨大的差異。直接回答標題中的問題 - Go並不比「012.Python」更快,Go比CPython快得多。

話雖如此,代碼示例並不是真的在做同樣的事情。 Python需要實例化其100萬個對象的int對象。 Go只是遞增一個內存位置。

0

@troq

我有點遲到了,但我想說的答案是yes和no。正如@gnibbler指出的那樣,CPython在簡單的實現中速度較慢,但​​是當您需要時,pypy會被編譯爲更快的代碼。

如果你正在用CPython進行數值處理,大多數會用numpy來完成數組處理,從而對數組和矩陣進行快速操作。最近我一直在做很多與numba,它允許你添加一個簡單的包裝到你的代碼。對於這一個,我只是將@njit添加到上面運行代碼的函數incALot()中。

在我的機器上,CPython需要61秒,但對於numba包裝器,它需要7.2微秒,這將與C類似,也許比Go更快。這是800萬次加速。所以,在Python中,如果數字看起來有點慢,那麼有工具可以解決它 - 而且你仍然可以獲得Python的程序員生產力和REPL。

def incALot(y): 
    x = 0 
    while x < y: 
     x += 1 

@njit('i8(i8)') 
def nbIncALot(y): 
    x = 0 
    while x < y: 
     x += 1 
    return x 

size = 1000000000 
start = time.time() 
incALot(size) 
t1 = time.time() - start 
start = time.time() 
x = nbIncALot(size) 
t2 = time.time() - start 
print('CPython3 takes %.3fs, Numba takes %.9fs' %(t1, t2)) 
print('Speedup is: %.1f' % (t1/t2)) 
print('Just Checking:', x) 

CPython3 takes 58.958s, Numba takes 0.000007153s 
Speedup is: 8242982.2 
Just Checking: 1000000000 
0

問題是Python被解釋了,GO不是這樣,所以沒有真正的方法來改變測試速度。解釋型語言通常(並不總是有一個虛擬組件)就是問題所在,你運行的任何測試都是在解釋範圍內運行,而不是實際運行時界限。在速度方面,Go比C稍慢,主要是由於它使用垃圾收集而不是手動內存管理。這說GO與Python相比速度很快,因爲它是一種編譯語言,GO中缺乏的唯一東西就是錯誤測試,如果我錯了,我會糾正錯誤。