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