未編譯的Python代碼比Go慢100倍,編譯后呢?
我是編譯型編程語言的忠實(shí)粉絲,一直都是。雖然解釋型編程語言可以讓開發(fā)者更快地編寫和測試代碼,但我仍然認(rèn)為編譯器是值得長期投入的。
在我看來,編譯型代碼有兩個明顯的優(yōu)勢:
每次修改代碼都可以得到驗(yàn)證,甚至是在開始運(yùn)行代碼之前。
更快的執(zhí)行速度。根據(jù)具體情況,代碼可能被編譯成非常底層的運(yùn)行指令。
我之所以要寫這篇文章,是想比較一下編譯型代碼的執(zhí)行速度會比解釋型快多少。
因?yàn)槲移珢劬幾g型編程語言,所以現(xiàn)在有個問題:我手頭有很多感興趣的代碼,但它們都是用 Python 寫的,我該怎么辦?全部重寫?部分重寫?完全不重寫?
先入之見
在這篇文章里,我通過比較 Java、Go 和 Python 在處理不同任務(wù)時的性能表現(xiàn)來驗(yàn)證我對它們的一些先入之見。首先是 Python,我正在考慮要不要把它替換掉。至于 Java,我已經(jīng)是 20 多年的粉絲了,一路看著它成熟,不管是性能還是功能都在變得更好。最后是 Go,我兩年前才開始用它,但真的很喜歡它。雖然 Go 相比 Java 還缺失了一些特性,比如類繼承,但它的語法簡潔而緊湊,編譯和執(zhí)行速度都很快,生成的代碼也很緊湊,還提供了優(yōu)雅的 goroutine 來實(shí)現(xiàn)并發(fā)處理。
以下是我的一些先入之見。
編譯型代碼的執(zhí)行速度比解釋型代碼要快一個數(shù)量級。之前,我比較了使用 JIT 和不使用 JIT 編譯 Java 代碼所獲得的性能,它們的比率大概是 30 比 1。
Go 的運(yùn)行速度比 Java 要快一點(diǎn)。我記得在之前的工作中做過一些測試,發(fā)現(xiàn) Go 在處理某些任務(wù)時要比 Java 快 30%,但最近一些文章又說 Java 比 Go 快。
先來測試一把
我在之前的一篇文章中通過一些代碼比較過 JIT 的性能,后來使用 Python 和 Go 也實(shí)現(xiàn)了一遍。這段代碼計算 100 的 Fibonacci 數(shù)值,每一輪計算 50 次,并打印執(zhí)行時間(納秒),共計算 200 輪。代碼可以在 https://github.com/rodrigoramirez/fibonacci 上找到。
三種語言的輸出結(jié)果看起來像這樣:
- Java Go Python
- ...
- 122 123 11683
- 119 107 11539
- 123 104 11358
- 120 115 11926
- 119 118 11973
- 120 104 11377
- 109 103 12960
- 127 122 15683
- 112 106 11482
- ...
平均值是這樣:
- Java Go Python
- 130 105 10050
可以看到,在計算 Fibonacci 數(shù)值時,Java 比 Go 要慢一些,大概慢 24%,而 Python 幾乎慢了 100 倍,也就是 9458%。
這個結(jié)果驗(yàn)證了我最初對 Java 和 Go 的判斷,但讓我感到吃驚的是 Python 的表現(xiàn),它慢得不只是一個數(shù)量級,是兩個!
我在想 Python 為什么會花這么多時間。
我首先想到的是,很多人關(guān)注的是 Python 的易用性,并通過犧牲性能來快速獲得處理結(jié)果。我相信數(shù)據(jù)科學(xué)家們都是這么想的。況且有這么多現(xiàn)成的庫可以用,為什么要去找其他的?遲早會有人優(yōu)化它們的。
第二個原因是很多人沒有比較過不同的實(shí)現(xiàn),因?yàn)楹芏喑鮿?chuàng)公司在激烈的競爭中忙于做出產(chǎn)品,根本無暇顧及什么優(yōu)化不優(yōu)化。
第三個原因,有一些方式可以讓同樣的 Python 代碼跑得更快。
把 Python 代碼編譯一下會如何
在做了一些調(diào)研之后,我決定使用 PyPy 測試一下相同的 Python 代碼。PyPy 是 Python 的另一個實(shí)現(xiàn),它本身就是使用 Python 開發(fā)的,包含了一個像 Java 那樣的 JIT 編譯器。跟 Java 一樣,我們需要忽略初始的輸出,并跳過 JIT 編譯過程,得到的結(jié)果如下:
- Java Go Python PyPy
- 130 105 10050 1887
PyPy 的平均響應(yīng)速度比 Python 快 5 倍,但仍然比 Go 慢 20 倍。
更多的測試
以上的測試主要集中在數(shù)值的計算上,如果回到最開始所說的 Python 代碼,我還需要關(guān)注:
- Kafka、HTTP 監(jiān)聽器和數(shù)據(jù)庫的 IO;
- 解析 JSON 消息。
總結(jié)
本文通過執(zhí)行簡單的數(shù)學(xué)運(yùn)算得出這樣的結(jié)論:Go 的執(zhí)行速度比 Java 快一些,比解釋運(yùn)行的 Python 快 2 個數(shù)量級。
基于這樣的結(jié)果,我個人是不會使用 Go 來替換 Java 的。
另一方面,在高負(fù)載的關(guān)鍵任務(wù)上使用 Python 不是一個好的選擇。如果你正面臨這種情況,可以考慮使用 Python 編譯器作為短期的應(yīng)急方案。
在決定是否要重寫 Python 代碼時,還需要考慮到其他因素,比如 IO 和 CPU 方面的問題,但這些超出本文的范圍了。
有人提醒我,使用 Go 和 Java 的 64 位整型只能準(zhǔn)確計算出 92 的 Fibonacci 數(shù)值,再往后會出現(xiàn)溢出(譯者:所以代碼后來改成了計算 90 的 Fibonacci 數(shù)值)。但即使是這樣,本文的結(jié)論仍然是有效的。