利用Psyco提升Python運行速度

來源:互聯網
上載者:User
Psyco 是嚴格地在 Python 運行時進行操作的。也就是說,Python 原始碼是通過 python 命令編譯成位元組碼的,所用的方式和以前完全相同(除了為調用 Psyco 而添加的幾個 import 語句和函數調用)。但是當 Python 解譯器運行應用程式時,Psyco 會不時地檢查,看是否能用一些專門的機器代碼去替換常規的 Python 位元組碼操作。這種專門的編譯和 Java 即時編譯器所進行的操作非常類似(一般地說,至少是這樣),並且是特定於體繫結構的。到現在為止,Psyco 只可用於 i386 CPU 體繫結構。Psyco 的妙處在於可以使用您一直在編寫的 Python 代碼(完全一樣!),卻可以讓它運行得更快。

Psyco 是如何工作的

要完全理解 Psyco,您可能需要很好地掌握 Python 解譯器的 eval_frame() 函數和 i386 組合語言。遺憾的是,我自己不能對其中任何一項發表專家性的意見 - 但是我想我可以大致不差地概述 Psyco。
在常規的 Python 中,eval_frame() 函數是 Python 解譯器的內迴圈。eval_frame() 函數主要察看執行內容中的當前位元組碼,並將控制向外切換到一個適合實現該位元組碼的函數。支援函數將做什麼的具體細節通常取決於儲存在記憶體中的各種 Python 對象的狀態。簡單點說,添加 Python 對象“2”和“3”和添加對象“5”和“6”會產生不同的結果,但是這兩個操作都以類似的方式指派。
Psyco 用複合求值單元替代 eval_frame() 函數。Psyco 有幾種方法可以用來改進 Python 所進行的操作。首先,Psyco 將操作編譯成有點最佳化的機器碼;由於機器碼需要完成的工作和 Python 的指派函數所要做的事一樣,所以其本身只有些許改進。而且,Psyco 編譯中的“專門的”內容不僅僅是對 Python 位元組碼的選擇,Psyco 也要對執行內容中已知的變數值進行專門化。例如,在類似於下面的代碼中,變數 x 在迴圈期間內是可知的:

代碼如下:


x = 5
l = []
for i in range(1000):
l.append(x*i)

該段代碼的最佳化版本不需要用“x 變數/對象的內容”乘每個 i,與之相比,簡單地用 5 乘以每個 i 所用的開銷較少,省略了尋找/間接引用這一步。
除為小型操作建立特定於 i386 的代碼之外,Psyco 還快取這個已編譯的機器碼以備今後重用。如果 Psyco 能夠識別出特定的操作和早先所執行的(“專門化的”)操作一樣,那麼,它就能依靠這個快取的代碼而不需要再次編譯程式碼片段。這樣就節省了一些時間。
但是,Psyco 中真正省時的原因在於 Psyco 將操作分成三個不同的層級。對於 Psyco,有“運行時”、“編譯時間”和“虛擬時”變數。Psyco 根據需要提高和降低變數的層級。運行時變數只是常規 Python 解譯器處理的原始位元組碼和對象結構。一旦 Psyco 將操作編譯成機器碼,那麼編譯時間變數就會在機器寄存器和可直接存取的記憶體位置中表示。
最有意思的層級是虛擬時變數。在內部,一個 Python 變數就是一個有許多成員組成的完整結構 - 即使當對象只代表一個整數時也是如此。Psyco 虛擬時變數代表了需要時可能會被構建的 Python 對象,但是這些對象的詳細資料在它們成為 Python 對象之前是被忽略的。例如,考慮如下賦值:
x = 15 * (14 + (13 - (12 / 11)))
標準的 Python 會構建和破壞許多個物件以計算這個值。構建一個完整的整數對象以儲存 (12/11) 這個值;然後從臨時對象的結構中“拉”出一個值並用它計算新的臨時對象 (13-PyInt)。而 Psyco 跳過這些對象,只計算這些值,因為它知道“如果需要”,可以從值建立一個對象。

使用 Psyco

解釋 Psyco 相對比較困難,但是使用 Psyco 就非常容易了。基本上,其全部內容就是告訴 Psyco 模組哪個函數/方法要“專門化”。任何 Python 函數和類本身的代碼都不需變更。
有幾種方法可以指定 Psyco 應該做什麼。“獵槍(shotgun)”方法使得隨處都可使用 Psyco 即時操作。要做到這點,把下列行置於模組頂端:

代碼如下:


import psyco ; psyco.jit()
from psyco.classes import *

第一行告訴 Psyco 對所有全域函數“發揮其魔力”。第二行(在 Python 2.2 及以上版本中)告訴 Psyco 對類方法執行相同的操作。為了更精確地確定 Psyco 的行為,可以使用下列命令:
psyco.bind(somefunc) # or method, class
newname = psyco.proxy(func)
第二種形式把 func 作為標準的 Python 函數,但是最佳化了涉及 newname 的調用。除了測試和調試之外的幾乎所有的情況下,您都將使用 psyco.bind() 形式。

Psyco 的效能

儘管 Psyco 如此神奇,使用它仍然需要一點思考和測試。主要是要明白 Psyco 對於處理多次迴圈的塊是很有用的,而且它知道如何最佳化涉及整數和浮點數的操作。對於非迴圈函數和其它類型對象的操作,Psyco 多半隻會增加其分析和內部編譯的開銷。而且,對於含有大量函數和類的應用程式來說,在整個應用程式範圍啟用 Psyco,會在機器碼編譯和用於這一快取的記憶體使用量方面增加大量的負擔。有選擇性地綁定那些可以從 Psyco 的最佳化中獲得最大收益的函數,這樣會好得多。
我以十分幼稚的方式開始了我的測試過程。我僅僅考慮了我近來啟動並執行、但還未考慮加速的應用程式。想到的第一個樣本是用來將我即將出版的書稿(Text Processing in Python)轉換成 LaTeX 格式的文本操作程式。該應用程式使用了一些字串方法、一些Regex和一些主要由Regex和字串匹配所驅動的程式邏輯。實際上將它用作 Psyco 的測試候選是很糟的選擇,但是我還是使用了,就這麼開始了。
第一遍測試中,我所做的就是將 psyco.jit() 添加到指令碼頂端。這做起來一點都不費力。遺憾的是,結果(意料當中)很令人失望。原先指令碼運行要花費 8.5 秒,經過 Psyco 的“加速”後它大概要運行 12 秒。真差勁!我猜測大概是即時編譯所需的啟動開銷拖累了已耗用時間。因此接下來我試著處理一個更大的輸入檔案(由原來那個輸入檔案的多個副本組成)。這次獲得了小小的成功,將已耗用時間從 120 秒左右減到了 110 秒。幾次運行中的加速效果比較一致,但是效果都不顯著。
本處理候選項的第二遍測試中。我只添加了 psyco.bind(main) 這一行,而不是添加一個總的 psyco.jit() 調用,因為 main() 函數確實要迴圈多次(但是僅利用了最少的整數運算)。這裡的結果名義上要比前面好。這種方法將正常的已耗用時間削減了十分之幾秒,在較大的輸入版本的情況下削減了數秒鐘。但是仍然沒有引入矚目的結果發生(但也沒產生什麼害處)。

為進行更恰當的 Psyco 測試,我搜尋出我在以前的文章裡編寫的一些神經網路代碼(請參閱“參考資料”)。這個“代碼辨識器(code_recognizer)”應用程式可以經“訓練”用於識別不同程式設計語言編寫的不同 ASCII 值的可能分布情況。類似於這樣的東西可能在猜測檔案類型方面(比方說丟失的網路資訊包)將很有用;但是,關於“訓練”些什麼,代碼實際上完全是通用的 - 它能很容易地學會識別面孔、聲音或潮汐模式。任何情況下,“代碼辨識器”都基於 Python 庫 bpnn,Psyco 4.0 分發版也包含(以修正的形式)了該庫作為測試案例。在本文中,對“代碼辨識器”要重點瞭解它做了許多浮點運算迴圈並花費了很長的已耗用時間。這裡我們已經有了一個能用於 Psyco 測試的好的候選用例。
使用了一段時間後,我建立了有關 Psyco 用法的一些詳細資料。對於這種只有少量類和函數的應用程式,使用即時綁定還是目標綁定沒有太大區別。但最佳的結果是,通過有選擇性地綁定最佳化類,仍可得到幾個百分點的改進。然而,更值得注意的是要理解 Psyco 綁定的範圍,這一點很重要。
code_recognizer.py 指令碼包括類似於下面的這些行:

從 bpnn 匯入 NN
class NN2(NN):
# customized output methods, math core inherited
也就是說,從 Psyco 的觀點來看,有趣的事情在類 bpnn.NN 之中。把 psyco.jit() 或 psyco.bind(NN2) 添加到 code_recognizer.py 指令碼中起不了什麼作用。要使 Psyco 進行期望的最佳化,需要將 psyco.bind(NN) 添加到 code_recognizer.py 或者將 psyco.jit() 添加到 bpnn.py。與您可能假設的情況相反,即時最佳化不在建立執行個體時或方法運行時發生,而是在定義類的範圍內發生。另外,綁定衍生類別不會專門化其從其它地方繼承的方法。
一旦找到適當的 Psyco 綁定的細微的詳細資料,那麼加速效果是相當明顯的。使用參考文章中提供的相同測試案例和訓練方法(500 個訓練模式,1000 個訓練迭代),神經網路訓練時間從 2000 秒左右減到了 600 秒左右 - 提速了 3 倍多。將迭代次數降到 10,加速的倍數也成比例降低(但對神經網路的識別能力無效),迭代的中間數值也會如此變化。
我發現使用兩行新代碼就能將已耗用時間從超過半小時減到 10 分鐘左右,效果非常顯著。這種加速仍可能比 C 編寫的類似應用程式的速度慢,而且它肯定比幾個獨立的 Psyco 測試案例所反映出的 100 倍加速要慢。但是這種應用程式是相當“真實的”,而且在許多環境中這些改進已經是夠顯著的了。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.