當在網上問為什麼Python比C語言更慢,回答最多的就是Python中有動態類型。然而,動態類型確實會在性能方面有影響,但是這並不是主要原因。 動態類型(像Python一樣的主要編程語言都一樣)使得編譯器很難優化性能。動態使得每次執行都可能很不同,編譯器難以優化。然而,正如Alex在談話中提到的,....
當在網上問為什麼Python比C語言更慢,回答最多的就是Python中有動態類型。然而,動態類型確實會在性能方面有影響,但是這並不是主要原因。
動態類型(像Python一樣的主要編程語言都一樣)使得編譯器很難優化性能。動態使得每次執行都可能很不同,編譯器難以優化。然而,正如Alex在談話中提到的,我們花費了數年的時間來研究究竟在運行時進行類型檢查的最好的辦法是什麼。但是沒什麼進展。
在現實中,在C語言和Python在運行時的巨大的不同是由於數據結構和演算法的不同。有時程式員也沒有註意到這一點。
用Python寫不同的代碼
讓我們用一個Alex提到的實例來說明問題。一個Python程式員可能很喜歡用下麵的例子表示一個平面上的點:
1 |
point = { 'x' : 0 , 'y' : 0 }
|
這種方法很易讀,容易編碼,形式很優雅。
另一個方面,一個C語言程式員可能使用結構體來表示平面上的點:
1 2 3 4 |
struct Point {
int x;
int y;
};
|
儘管這種方法也和Python能一樣的工作並且都是很優雅的,但這是完全不同的數據結構。這裡我們告訴了編譯器,我們有兩個欄位x和y。知道了這兩個欄位的類型,編譯器將分配一塊連續的記憶體來儲存這兩個數據。換一句話說,就像一個數組一樣。任何時間,編譯器都知道給定的x和y在哪裡。我們可以很容易地訪問這些數據,就像是訪問某些常數據一樣。
Python使用哈希散列的方法來解決類似的問題。所以編譯器不能簡單地分配連續記憶體存儲x和y來處理這些問題。由於我們在其中任意的地方都可能出現這些鍵。如果我們想的話,我們也可能刪除這些鍵。編譯器必須要使用哈希函數來映射到你可能讓他指向的任何存儲單元。不用說,這些函數增加了處理時間。儘管也許減緩的很小,但是足可以拖慢你的代碼,尤其是這種情況如果很多的時候。
如果就是想將Python翻譯成C語言的話,可能就像下麵這樣:
1 2 3 |
std::hash_set<span> point;
point[“x”] = x
point[“y”] = y< / span>
|
看這個代碼片段,好像就是語言的設計者他們自己故意儘力使哈希表複雜,因此儘管是正確的,但沒有人使用。由於這個原因,寫C語言的人可能認為這是不可思議的,但為什麼在Python就是可以接受的呢?
原因就是寫Python代碼的人的“dictionaries are lightweight objects”這種心態。看下麵的代碼,這在Python中最接近C語言結構體:
1 2 3 4 |
class Point( object ):
x, y = None , None
def __init__( self , x, y):
self .x, self .y = x, y
|
這對編譯器是有用的,就像是C語言的結構體。例如第二行,我們明確告訴編譯器但我們創造一個對象時我們總是至少需要兩個數據段,我們希望編譯器處理這個問題。
不幸的是這種標準的Python被叫做CPthon,不能總被使用。在我的機器上,下麵的代碼要執行186毫秒:
1 2 3 4 5 6 |
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
|
在我的機器上,用point.x代替point['x']會花費201毫秒。也就是說,會慢了8%。
在CPthon中,point.x通常就是被處理成dict(point)['x']。這意味著帶著點的class仍然像以前一樣使用字典(dictionary)的方法查找。這樣的話,就很容易看出為什麼directionary的方法被看為“輕量級的”。
一些Python寫的代碼就是為了效率而設計的,例如PyPy,能很快地執行。如果不使用Python而是使用PyPy,同樣的代碼片段執行時間分別是21.6和3.75毫秒。這種方法相比CPython在JIT-capable編譯情況下結果都是令人滿意的。換一句話說,PyPy能正確地使用數據結構。
我希望你再一次看這個最短時間3.75毫秒。這個數字表明我們能在一秒進行266000次運算,這些事來自Python的,其中有動態綁定,monkey-patching(在不改變源代碼的情況下擴展或修改動態語言運行時代碼的方法)等。所有的這些,都是在編碼和實現中使用了更好的數據結構。下一次當你在用Python寫一行代碼時,想一想你在使用什麼數據結構,顯示的還是隱式的,考慮一下是否有更好的辦法。這就是你用C語言寫程式時考慮的,不是嗎?
最後,我願意相信這個文章是表明為什麼Python是一個有前途的語言的一個清楚的例子(或者是類似的語言)。這表明瞭標準的Python實現,這裡的CPython僅僅是作為一個參考,它從來就不是被設計用來更快地執行的。正如我們今天可以看到的,像PyPy一樣的演算法實現是可以優化你的代碼到一個很好的長度。隨著語言的自然發展,這些優化是可能的。我們僅僅用Python編程過23年,那麼如果像C語言一樣有42年的發展,Python會是什麼樣子呢?
1、有人也許會爭辯說collections.namedtuple()是更接近於C語言的結構體,這是對的,但我們不要過分將事情複雜化,我們的重點是有效。
2、為了更清楚python是怎樣工作的,請參考python文檔。