乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      用Psyco讓Python運行得像C一樣快

       sofes 2012-09-11
      作者:來自ITPUB論壇  2008-01-18

        【IT168 技術(shù)文檔】Python 的設(shè)計在很多方面都類似于 Java 的設(shè)計。兩者都利用了解釋專門的偽編譯字節(jié)碼的虛擬機。JVM 比 Python 更高級的一個方面在于優(yōu)化了字節(jié)碼的執(zhí)行。Psyco,一種 Python 專用編譯器,幫助平衡了這一競爭。Psyco 現(xiàn)在是個外部模塊,但是在將來的某一天它可能會包括到 Python 本身中去。只需極少量的額外編程,通常就可以使用 Psyco 將 Python 代碼的速度提高好幾個數(shù)量級。在本文中,David Mertz 研究了 Psyco 是什么,并在一些應用程序中對它進行了測試。

        Python 對于您想讓它做的事來說通常夠快了。編程新手對于類似 Python 這樣的解釋型/字節(jié)編譯型語言,將 90% 的關(guān)注點集中在執(zhí)行速度方面,是相當幼稚的。在最新的硬件上,大多數(shù)非優(yōu)化的 Python 程序運行的速度和所需要達到的速度一樣快,而且,花費額外的編程工作以使應用程序運行得更快實在沒什么意義。

        因此,在本文,我只對其它的百分之十感興趣。有時,Python 程序(或用其它語言編寫的程序)也會運行得極其緩慢。不同的目的所需要的改進差異很大;提高只運行幾毫秒的任務的性能極少能引人注目,但是加快那些需運行幾分鐘、幾小時、幾天甚至幾星期的任務的速度通常是很值得的。而且,應該注意到并不是所有任務運行緩慢的原因都是由 CPU 引起的。例如,如果完成一個數(shù)據(jù)庫查詢要花費幾個小時,那么處理結(jié)果數(shù)據(jù)集要花費一分鐘還是兩分鐘就沒什么差別了。本文同樣不討論與 I/O 有關(guān)的問題。

        有很多方法可以加速 Python 程序。每個程序員都應當想到的第一種技術(shù)就是改進所使用的算法和數(shù)據(jù)結(jié)構(gòu)。對低效算法步驟進行細微的優(yōu)化是徒勞無益的事情。例如,如果當前技術(shù)的復雜性等級是 O(n**2),那么將這些步驟加速 10 倍遠不及尋找 O(n) 替代品來得有用。即使在考慮用匯編語言重寫算法這種極端情況時,這種思想也都適用:Python 中正確的算法通常會比手工調(diào)優(yōu)的匯編語言中的錯誤算法快得多。

        第二種您應當首先考慮的技術(shù)是概要分析您的 Python 應用程序,要著眼于將關(guān)鍵部分重寫成 C 擴展模塊。使用像 SWIG 這樣的擴展封裝器(請參閱參考資料),可以創(chuàng)建 C 擴展,它將程序中最耗時元素作為 C 代碼執(zhí)行。以這種方式擴展 Python 相對簡單,但要花些時間學習(并且需要了解 C 的知識)。您經(jīng)常會發(fā)現(xiàn)執(zhí)行 Python 應用程序所花費的時間絕大部分只是花在了幾個函數(shù)上,因此,采用這種擴展可能會有很可觀的“成果”。

        第三種技術(shù)建立在第二種技術(shù)的基礎(chǔ)之上。Greg Ewing 已經(jīng)創(chuàng)建了名叫 Pyrex 的語言,該語言融合了 Python 和 C。特別地,要使用 Pyrex,需要用類似 Python 的語言編寫函數(shù),這種語言將類型聲明添加到所選變量中。Pyrex(工具)將“.pyx”文件處理成“.c”擴展名的文件。一旦用 C 編譯器進行了編譯,就可以將這些 Pyrex(語言)模塊導入常規(guī)的 Python 應用程序并使用。由于 Pyrex 使用的語法和 Python 本身的語法(包括循環(huán)、分支和異常語句、賦值方式、塊縮進等等)幾乎一樣,因此 Pyrex 程序員不需要學會用 C 去編寫擴展。而且,與直接用 C 編寫擴展相比,Pyrex 允許在同一代碼中更無縫地混合 C 級別的變量和 Python 級別的變量(對象)。

        最后一種技術(shù)就是本文的主題。擴展模塊 Psyco 可以插入 Python 解釋器的內(nèi)部,而且可以有選擇性地用優(yōu)化的機器代碼去替換部分 Python 的解釋型字節(jié)碼。和所描述的其它技術(shù)不同,Psyco 是嚴格地在 Python 運行時進行操作的。也就是說,Python 源代碼是通過 python 命令編譯成字節(jié)碼的,所用的方式和以前完全相同(除了為調(diào)用 Psyco 而添加的幾個 import 語句和函數(shù)調(diào)用)。但是當 Python 解釋器運行應用程序時,Psyco 會不時地檢查,看是否能用一些專門的機器代碼去替換常規(guī)的 Python 字節(jié)碼操作。這種專門的編譯和 Java 即時編譯器所進行的操作非常類似(一般地說,至少是這樣),并且是特定于體系結(jié)構(gòu)的。到現(xiàn)在為止,Psyco 只可用于 i386 CPU 體系結(jié)構(gòu)。Psyco 的妙處在于可以使用您一直在編寫的 Python 代碼(完全一樣!),卻可以讓它運行得更快。

        Psyco 是如何工作的

        要完全理解 Psyco,您可能需要很好地掌握 Python 解釋器的 eval_frame() 函數(shù)和 i386 匯編語言。遺憾的是,我自己不能對其中任何一項發(fā)表專家性的意見 - 但是我想我可以大致不差地概述 Psyco。

        在常規(guī)的 Python 中,eval_frame() 函數(shù)是 Python 解釋器的內(nèi)循環(huán)。eval_frame() 函數(shù)主要察看執(zhí)行上下文中的當前字節(jié)碼,并將控制向外切換到一個適合實現(xiàn)該字節(jié)碼的函數(shù)。支持函數(shù)將做什么的具體細節(jié)通常取決于保存在內(nèi)存中的各種 Python 對象的狀態(tài)。簡單點說,添加 Python 對象“2”和“3”和添加對象“5”和“6”會產(chǎn)生不同的結(jié)果,但是這兩個操作都以類似的方式分派。

        Psyco 用復合求值單元替代 eval_frame() 函數(shù)。Psyco 有幾種方法可以用來改進 Python 所進行的操作。首先,Psyco 將操作編譯成有點優(yōu)化的機器碼;由于機器碼需要完成的工作和 Python 的分派函數(shù)所要做的事一樣,所以其本身只有些許改進。而且,Psyco 編譯中的“專門的”內(nèi)容不僅僅是對 Python 字節(jié)碼的選擇,Psyco 也要對執(zhí)行上下文中已知的變量值進行專門化。例如,在類似于下面的代碼中,變量 x 在循環(huán)持續(xù)時間內(nèi)是可知的:

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

        該段代碼的優(yōu)化版本不需要用“x 變量/對象的內(nèi)容”乘每個 i,與之相比,簡單地用 5 乘以每個 i 所用的開銷較少,省略了查找/間接引用這一步。

        除為小型操作創(chuàng)建特定于 i386 的代碼之外,Psyco 還高速緩存這個已編譯的機器碼以備今后重用。如果 Psyco 能夠識別出特定的操作和早先所執(zhí)行的(“專門化的”)操作一樣,那么,它就能依靠這個高速緩存的代碼而不需要再次編譯代碼段。這樣就節(jié)省了一些時間。

        但是,Psyco 中真正省時的原因在于 Psyco 將操作分成三個不同的級別。對于 Psyco,有“運行時”、“編譯時”和“虛擬時”變量。Psyco 根據(jù)需要提高和降低變量的級別。運行時變量只是常規(guī) Python 解釋器處理的原始字節(jié)碼和對象結(jié)構(gòu)。一旦 Psyco 將操作編譯成機器碼,那么編譯時變量就會在機器寄存器和可直接訪問的內(nèi)存位置中表示。

        最有意思的級別是虛擬時變量。在內(nèi)部,一個 Python 變量就是一個有許多成員組成的完整結(jié)構(gòu) - 即使當對象只代表一個整數(shù)時也是如此。Psyco 虛擬時變量代表了需要時可能會被構(gòu)建的 Python 對象,但是這些對象的詳細信息在它們成為 Python 對象之前是被忽略的。例如,考慮如下賦值:

        x = 15 * (14 + (13 - (12 / 11)))

        標準的 Python 會構(gòu)建和破壞許多對象以計算這個值。構(gòu)建一個完整的整數(shù)對象以保存 (12/11) 這個值;然后從臨時對象的結(jié)構(gòu)中“拉”出一個值并用它計算新的臨時對象 (13-PyInt)。而 Psyco 跳過這些對象,只計算這些值,因為它知道“如果需要”,可以從值創(chuàng)建一個對象。

        使用 Psyco

        解釋 Psyco 相對比較困難,但是使用 Psyco 就非常容易了?;旧?,其全部內(nèi)容就是告訴 Psyco 模塊哪個函數(shù)/方法要“專門化”。任何 Python 函數(shù)和類本身的代碼都不需進行更改。

        有幾種方法可以指定 Psyco 應該做什么?!矮C槍(shotgun)”方法使得隨處都可使用 Psyco 即時操作。要做到這點,把下列行置于模塊頂端:

        import psyco ; psyco.jit()

        from psyco.classes import *

        第一行告訴 Psyco 對所有全局函數(shù)“發(fā)揮其魔力”。第二行(在 Python 2.2 及以上版本中)告訴 Psyco 對類方法執(zhí)行相同的操作。為了更精確地確定 Psyco 的行為,可以使用下列命令:

        psyco.bind(somefunc) # or method, class

        newname = psyco.proxy(func)

        第二種形式把 func 作為標準的 Python 函數(shù),但是優(yōu)化了涉及 newname 的調(diào)用。除了測試和調(diào)試之外的幾乎所有的情況下,您都將使用 psyco.bind() 形式。

        Psyco 的性能

        盡管 Psyco 如此神奇,使用它仍然需要一點思考和測試。主要是要明白 Psyco 對于處理多次循環(huán)的塊是很有用的,而且它知道如何優(yōu)化涉及整數(shù)和浮點數(shù)的操作。對于非循環(huán)函數(shù)和其它類型對象的操作,Psyco 多半只會增加其分析和內(nèi)部編譯的開銷。而且,對于含有大量函數(shù)和類的應用程序來說,在整個應用程序范圍啟用 Psyco,會在機器碼編譯和用于這一高速緩存的內(nèi)存使用方面增加大量的負擔。有選擇性地綁定那些可以從 Psyco 的優(yōu)化中獲得最大收益的函數(shù),這樣會好得多。

        我以十分幼稚的方式開始了我的測試過程。我僅僅考慮了我近來運行的、但還未考慮加速的應用程序。想到的第一個示例是用來將我即將出版的書稿(Text Processing in Python)轉(zhuǎn)換成 LaTeX 格式的文本操作程序。該應用程序使用了一些字符串方法、一些正則表達式和一些主要由正則表達式和字符串匹配所驅(qū)動的程序邏輯。實際上將它用作 Psyco 的測試候選是很糟的選擇,但是我還是使用了,就這么開始了。

        第一遍測試中,我所做的就是將 psyco.jit() 添加到腳本頂端。這做起來一點都不費力。遺憾的是,結(jié)果(意料當中)很令人失望。原先腳本運行要花費 8.5 秒,經(jīng)過 Psyco 的“加速”后它大概要運行 12 秒。真差勁!我猜測大概是即時編譯所需的啟動開銷拖累了運行時間。因此接下來我試著處理一個更大的輸入文件(由原來那個輸入文件的多個副本組成)。這次獲得了小小的成功,將運行時間從 120 秒左右減到了 110 秒。幾次運行中的加速效果比較一致,但是效果都不顯著。

        本處理候選項的第二遍測試中。我只添加了 psyco.bind(main) 這一行,而不是添加一個總的 psyco.jit() 調(diào)用,因為 main() 函數(shù)確實要循環(huán)多次(但是僅利用了最少的整數(shù)運算)。這里的結(jié)果名義上要比前面好。這種方法將正常的運行時間削減了十分之幾秒,在較大的輸入版本的情況下削減了數(shù)秒鐘。但是仍然沒有引入矚目的結(jié)果發(fā)生(但也沒產(chǎn)生什么害處)。

        為進行更恰當?shù)?Psyco 測試,我搜尋出我在以前的文章里編寫的一些神經(jīng)網(wǎng)絡(luò)代碼(請參閱“參考資料”)。這個“代碼識別器(code_recognizer)”應用程序可以經(jīng)“訓練”用于識別不同編程語言編寫的不同 ASCII 值的可能分布情況。類似于這樣的東西可能在猜測文件類型方面(比方說丟失的網(wǎng)絡(luò)信息包)將很有用;但是,關(guān)于“訓練”些什么,代碼實際上完全是通用的 - 它能很容易地學會識別面孔、聲音或潮汐模式。任何情況下,“代碼識別器”都基于 Python 庫 bpnn,Psyco 4.0 分發(fā)版也包含(以修正的形式)了該庫作為測試用例。在本文中,對“代碼識別器”要重點了解它做了許多浮點運算循環(huán)并花費了很長的運行時間。這里我們已經(jīng)有了一個能用于 Psyco 測試的好的候選用例。

        使用了一段時間后,我建立了有關(guān) Psyco 用法的一些詳細信息。對于這種只有少量類和函數(shù)的應用程序,使用即時綁定還是目標綁定沒有太大區(qū)別。但最佳的結(jié)果是,通過有選擇性地綁定最優(yōu)化類,仍可得到幾個百分點的改進。然而,更值得注意的是要理解 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 進行期望的優(yōu)化,需要將 psyco.bind(NN) 添加到 code_recognizer.py 或者將 psyco.jit() 添加到 bpnn.py。與您可能假設(shè)的情況相反,即時優(yōu)化不在創(chuàng)建實例時或方法運行時發(fā)生,而是在定義類的作用域內(nèi)發(fā)生。另外,綁定派生類不會專門化其從其它地方繼承的方法。

        一旦找到適當?shù)?Psyco 綁定的細微的詳細信息,那么加速效果是相當明顯的。使用參考文章中提供的相同測試用例和訓練方法(500 個訓練模式,1000 個訓練迭代),神經(jīng)網(wǎng)絡(luò)訓練時間從 2000 秒左右減到了 600 秒左右 - 提速了 3 倍多。將迭代次數(shù)降到 10,加速的倍數(shù)也成比例降低(但對神經(jīng)網(wǎng)絡(luò)的識別能力無效),迭代的中間數(shù)值也會如此變化。

        我發(fā)現(xiàn)使用兩行新代碼就能將運行時間從超過半小時減到 10 分鐘左右,效果非常顯著。這種加速仍可能比 C 編寫的類似應用程序的速度慢,而且它肯定比幾個獨立的 Psyco 測試用例所反映出的 100 倍加速要慢。但是這種應用程序是相當“真實的”,而且在許多環(huán)境中這些改進已經(jīng)是夠顯著的了。

        Psyco 將何去何從?

        Psyco 現(xiàn)在不執(zhí)行任何類型的內(nèi)部統(tǒng)計或概要分析,只對生成的機器碼進行最小優(yōu)化??赡芙窈蟮陌姹緯廊绾吾槍δ切┠苷嬲畲螳@益 Python,并且丟棄為不可優(yōu)化部分高速緩存的機器碼。另外,也許今后的 Psyco 可能會決定對費力運行的操作進行更廣泛(但也更昂貴)的優(yōu)化。這種運行時分析可能類似于 Sun 的 HotSpot 技術(shù)為 Java 所做的工作。Java 不像 Python,它有類型聲明,但這一事實實際上沒有許多人所設(shè)想的那樣重要(但是先前在 Self、Smalltalk、Lisp 和 Scheme 的優(yōu)化方面所做的工作也說明了這一點)。

        若將 Psyco 類型的技術(shù)集成到 Python 本身的某個未來版本中去,會多么令人激動,盡管我懷疑這永遠不會真正發(fā)生。添加幾行導入和綁定代碼不需要做很多工作,但卻可以輕易地讓 Python 比以前運行得快得多。我們將看到這一點。

        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多