最近在leetcode刷題,明顯的注意到同樣的算法,python運行的要慢的多,查資料得到python運行的慢主要原因如下:
一、動態類型導致運行速度慢,在北郵人論壇里面的這篇帖子中有較為詳細的解釋,原文中有舉例說明,本文沒有例子講解只是提取了原理來講解,內容主要如下:
(原文鏈接:http://bbs.byr.cn/#!article/Python/68)
a、動態語言中的執行過程
Python等動態類型語言之所以慢,就是因為每一個簡單的操作都需要大量的指令才能完成。他們的虛擬機擁有很強的優化器,卻是為靜態語言設計的。對Python幾乎沒有效果。舉一個例子。對於整數加法,C語言很簡單,只要一個機器指令ADD就可以了,最多不過再加一些內存讀寫。但是,對於Python來說,a+b這樣的簡單二元運算,可就真的很麻煩了。Python是動態語言,變量只是對象的引用,變量a和b本身都沒有類型,而它們的值有類型。所以,在相“加”之前,必須先判斷類型。
1. 判斷a是否為整數,否則跳到第9步
2. 判斷b是否為整數,否則跳到第9步
3. 將a指向的對象中的整數值讀出來
4. 將b指向的對象中的整數值讀出來
5. 進行整數相加
6. 生成一個新整數對象
7. 將運算結果存進去
8. 返回這個對象,完成!
9. 判斷a是否為字符串,否則跳到第13步
10. 判斷b是否為字符串,否則跳到第13步
11. 進行字符串串接操作,生成一個新字符串對象
12. 返回這個對象,完成!
13. 從a的字典里取出__add__方法
14. 調用這個方法,將a和b作為參數傳入
15. 返回上述方法的返回值。
這還只是簡化版的,實際中還要考慮溢出問題等。
b、Jpython
Jython能做的只是把Python代碼轉換成JVM的代碼,而Python中那些判斷a,b是否為整數或者字符串的操作是不能省略的。畢竟Python是允許你寫1+2同時也可以"hello"+"world"。這種運行時的類型檢查並不能簡單地通過編譯而去除。
c、谷歌的Unladen Swallow項目
它是一個很有雄心的項目,但是,在項目開始一年后就流產了。最后,加速效果也不過50%左右。它們使用的方法是朴素的“模板編譯法”:看到Python的加法操作,就轉換成一個C語言的函數調用,調用Python的PyNumber_Add函數。這個函數就是干類似上面一串的事。同樣地,雖然去除了官方Python的解釋器代價,但並沒有消除運行時類型檢查的代價。
d、pypy為什么比較快和pypy的不足:
PyPy可以將Python的速度加到C的一半左右。PyPy使用了一種技巧,就是“類型推導”(Type Inference)。PyPy的運行時編譯器(Just-in-time compiler,或者稱JIT Compiler)的工作方式是,只優化循環,因為大量的時間都是消耗在少數循環上。當運行時檢測到某個循環運行的次數很多的時候,就開啟一個“錄像機”,錄制這個循環執行一次中,執行的所有操作的軌跡。這樣以“軌跡”為單位的編譯方式叫Tracing JIT。當類型確定以后,其中涉及的數據都是整數,都可以直接對應機器指令進行執行。程序起碼在這一部分已經由動態的代碼變成像C一樣的靜態類型代碼了,而且數據類型很接近機器。將這一段代碼編譯成機器碼,效率就可以和C相比了。
注意到,這其實是一種“猜測”:優化器“猜想”每次執行循環for i in range(n),i和n都是整數。這種猜測是可能出錯的。萬一程序員將一個字符串傳入函數怎么辦呢?所以,基於“猜測”(speculation)的優化必須考慮“猜錯了”的情形。這就是優化過的代碼的第1、3、11行的用途。1和3考慮萬一i和n不是整數的情形,而15考慮了整數溢出的情況。在Python里,整數都是高精度整數,可以是任意大的,而不僅限於32位。(其實上述32位也只是假設,在64位機上,顯然64位效率更高。)所以,如果猜錯了(這種事經常會發生),就必須停止執行這段“優化”過的代碼,而是老老實實回到解釋器中,像傳統的Python一樣執行。
可以看出,帶有類型推導功能的Tracing JIT編譯器可以大幅度加快動態語言的速度。主要原因是:
1. 在運行時得到了變量的類型,並通過“猜測”,將這些類型轉換成接近機器的類型。
2. 將簡化的操作編譯成機器碼,去除了解釋器的代價。
目前,PyPy是一個很活躍的項目。但是,畢竟是一個研究型的項目,PyPy也有自己的不足。如和官方Python並不完全兼容;PyPy本身的可執行文件很大;並不是運行所有的程序都快——PyPy雖然JIT Compiler很快,但它的解釋器速度不如官方的Python,對於無法通過優化加速的程序來說,PyPy就不快了。
二、python慢不僅是因為動態類型,甚至不是python慢的主要原因,在現實中,在C語言和Python在運行時的巨大的不同是由於數據結構和算法的不同。
原文鏈接:http://ourjs.com/detail/5320393ab79767cf7b000004
用Python寫不同的代碼
point = {'x': 0, 'y': 0}
struct Point {
int x;
int y;
};
std::hash_set point;
point[“x”] = x
point[“y”] = y
class Point(object):
x, y = None, None
def __init__(self, x, y):
self.x, self.y = x, y
def sum_(points):
sum_x, sum_y = 0, 0
for point in points:
sum_x += point['x']
sum_y += point['y']
return sum_x, sum_y