小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

用Psyco讓Python運(yùn)行得像C一樣快

 sofes 2012-09-11
作者:來(lái)自ITPUB論壇  2008-01-18

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

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

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

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

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

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

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

  Psyco 是如何工作的

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

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

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

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

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

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

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

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

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

  標(biāo)準(zhǔn)的 Python 會(huì)構(gòu)建和破壞許多對(duì)象以計(jì)算這個(gè)值。構(gòu)建一個(gè)完整的整數(shù)對(duì)象以保存 (12/11) 這個(gè)值;然后從臨時(shí)對(duì)象的結(jié)構(gòu)中“拉”出一個(gè)值并用它計(jì)算新的臨時(shí)對(duì)象 (13-PyInt)。而 Psyco 跳過(guò)這些對(duì)象,只計(jì)算這些值,因?yàn)樗馈叭绻枰?,可以從值?chuàng)建一個(gè)對(duì)象。

  使用 Psyco

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

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

  import psyco ; psyco.jit()

  from psyco.classes import *

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

  psyco.bind(somefunc) # or method, class

  newname = psyco.proxy(func)

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

  Psyco 的性能

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

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

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

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

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

  使用了一段時(shí)間后,我建立了有關(guān) Psyco 用法的一些詳細(xì)信息。對(duì)于這種只有少量類和函數(shù)的應(yīng)用程序,使用即時(shí)綁定還是目標(biāo)綁定沒(méi)有太大區(qū)別。但最佳的結(jié)果是,通過(guò)有選擇性地綁定最優(yōu)化類,仍可得到幾個(gè)百分點(diǎn)的改進(jìn)。然而,更值得注意的是要理解 Psyco 綁定的作用域,這一點(diǎn)很重要。

  code_recognizer.py 腳本包括類似于下面的這些行:

  從 bpnn 導(dǎo)入 NN

  class NN2(NN):

  # customized output methods, math core inherited

  也就是說(shuō),從 Psyco 的觀點(diǎn)來(lái)看,有趣的事情在類 bpnn.NN 之中。把 psyco.jit() 或 psyco.bind(NN2) 添加到 code_recognizer.py 腳本中起不了什么作用。要使 Psyco 進(jìn)行期望的優(yōu)化,需要將 psyco.bind(NN) 添加到 code_recognizer.py 或者將 psyco.jit() 添加到 bpnn.py。與您可能假設(shè)的情況相反,即時(shí)優(yōu)化不在創(chuàng)建實(shí)例時(shí)或方法運(yùn)行時(shí)發(fā)生,而是在定義類的作用域內(nèi)發(fā)生。另外,綁定派生類不會(huì)專門(mén)化其從其它地方繼承的方法。

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

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

  Psyco 將何去何從?

  Psyco 現(xiàn)在不執(zhí)行任何類型的內(nèi)部統(tǒng)計(jì)或概要分析,只對(duì)生成的機(jī)器碼進(jìn)行最小優(yōu)化。可能今后的版本會(huì)知道如何針對(duì)那些能真正最大獲益 Python,并且丟棄為不可優(yōu)化部分高速緩存的機(jī)器碼。另外,也許今后的 Psyco 可能會(huì)決定對(duì)費(fèi)力運(yùn)行的操作進(jìn)行更廣泛(但也更昂貴)的優(yōu)化。這種運(yùn)行時(shí)分析可能類似于 Sun 的 HotSpot 技術(shù)為 Java 所做的工作。Java 不像 Python,它有類型聲明,但這一事實(shí)實(shí)際上沒(méi)有許多人所設(shè)想的那樣重要(但是先前在 Self、Smalltalk、Lisp 和 Scheme 的優(yōu)化方面所做的工作也說(shuō)明了這一點(diǎn))。

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

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

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多