英文原文:Why Dynamic Programming Languages Are Slow,翻譯:iteye
靜態型別語言中,在宣告變數時已經指定了資料型別和表示方法。動態型別語言是在執行期間檢查資料的型別,不得不保持描述變數值的實際型別標記,程式在每次操作變數時,需要執行資料依賴分支。
而間接分支(Indirect branch)和資料區域性性(data locality)對於執行時的效能是致命的。
這就是動態語言的JIT編譯器基準測試要強調near-C的內迴圈速度,以及避免大的資料結構和資料處理問題的原因。
我也希望像Python這樣的動態型別語言可以變快。我試過用Python來進行傳統的伺服器程式設計——“系統語言”領域——但是效果真的不好,我現在考慮用Java重寫一個伺服器。
因此,我花時間思考如何真正靜態地編譯Python。畢竟,那是我夢寐以求的程式語言!但當我思考如何將動態型別程式碼與靜態編譯Python結合起來時,我遇到了資料變慢的問題:
在動態語言中,通常所有陣列中的元素(或其他資料結構)型別各不相同,所以有不同的表示值。因此,這些值都必須被單獨存放為堆,而不是順序地存為陣列。這意味著如果對不相鄰的記憶體執行資料依賴分支,則對快取有更高的要求。
也有一些聰明的技巧,使用變數中的特殊bit,將一些原生型別(像整型)打包成一個型別,類似於指標,但這要求暫存器在操作過程中進行跟蹤,會增加開銷。
還有一些方法,比如使用JIT編譯熱路徑(hot path)時,如果你直接插入沒有標型別的值,而不是在堆裡分別標記型別, 那麼與JIT編譯過的程式碼的互操作性會降低,如果其他程式碼改變了陣列中的一個值的型別,就會出現非常嚴重的後果。
我一直在思考,在Python語言中,什麼是靜態的,什麼不是。通過SSTA(統計靜態分析)和逃逸分析可以判斷,大量正常的程式是靜態的。Paul Biggar給了我信心證實我的猜測是正確的,我的Python程式碼90%都是靜態的。
有人會問,那另外的10%呢?通常情況下,我可以讓所有的都是靜態的,或者想象它被引數型別的限制範圍特殊化了。除了Python語言的標準模 式,其他模式都由Web伺服器分配給基於HTTP方法(如果收到GET請求就稱為“get”方法)的Web處理器,這也需要程式設計師依照switch語句 (如elifs的長鏈)來進行修改。
Robert Harper對“從單一型別靜態語言方面,動態語言是如何實現的”這個問題作出了很好的解釋,下面這句話是我希望他能進一步進行解釋的:
引用
我深知“編譯器可以優化它”,至少在某些情況下。
我確信他說的“某些情況”是指遇到non-escaping的情況,因為和後面的執行程式碼進行互動時,你應該要能夠確定escape的型別。
一些動態呼叫是無汙染的——編譯器可以從程式碼檢查中發現一些變數(或方法)是動態的,但動態的程式碼不表示其他變數也是動態的,因為不同型別的變數、方法、成員的存在或缺失都被限制成了可識別的型別(或null)。
但通常編譯器是無法從程式碼檢查中發現這些情況的,如果無法追蹤到執行的情況,就無法知道程式碼如何依賴以及如何改變其他靜態變數的值。因此,工作中斷,所有變數再次變為動態的。
我一直在努力尋找把Monkey Patch(不改變初始原始碼來擴充套件和修改動態語言執行程式碼的方法)、Set(屬性或索引器元素賦值的“訪問器”方法)、SetAttr(SetAttr 語句可以為一個檔案設定屬性資訊)等解決辦法移植到我虛構的Python編譯器裡,因為型別標記嚴重地降低了執行效能。
快速的資料結構對於記憶體訪問模式和快取位置是非常重要的,還可以減少分支和對這些分支的標記工作。
Jonathan Shapiro的文章Programming Language Challenges in Systems Codes非常棒,我很贊同文中的觀點。