前言
總的來(lái)說(shuō),人們使用python是因?yàn)樗奖闱乙子诰幊痰炒埽皇且驗(yàn)樗芸燹忠4罅康牡谌綆?kù)和對(duì)Python的工業(yè)支持的廣泛性彌補(bǔ)了它沒(méi)有Java或C的原始性能。開(kāi)發(fā)速度優(yōu)先于執(zhí)行速度幌衣。
但在許多情況下矾削,它不必是一個(gè)非此即彼的命題。適當(dāng)?shù)貎?yōu)化豁护,Python應(yīng)用程序可以以驚人的速度運(yùn)行——也許不是Java或C快僵控,但對(duì)于Web應(yīng)用程序萝勤、數(shù)據(jù)分析沸呐、管理和自動(dòng)化工具以及其他大多數(shù)目的來(lái)說(shuō)都足夠快优训。實(shí)際上,您可能會(huì)忘記您是在用應(yīng)用程序性能來(lái)?yè)Q取開(kāi)發(fā)人員的生產(chǎn)力班缎。
優(yōu)化python性能并不能歸結(jié)為任何一個(gè)因素蝴光。相反,它是關(guān)于應(yīng)用所有可用的最佳實(shí)踐吝梅,并選擇最適合當(dāng)前場(chǎng)景的最佳實(shí)踐虱疏。(Dropbox中的人們有一個(gè)關(guān)于Python優(yōu)化功能最引人注目的例子惹骂。)
在這篇文章中苏携,我將概述許多常見(jiàn)的Python優(yōu)化。其中一些是只需將一個(gè)項(xiàng)目切換到另一個(gè)項(xiàng)目(如更改python解釋器)即可實(shí)現(xiàn)的嵌入式度量对粪,但實(shí)現(xiàn)最大回報(bào)的項(xiàng)目將需要更詳細(xì)的工作右冻。
1装蓬。Memoize(緩存)重復(fù)使用的數(shù)據(jù)
當(dāng)你能做到一次并保存結(jié)果時(shí),千萬(wàn)不要做一千次工作纱扭。如果有一個(gè)經(jīng)常調(diào)用的函數(shù)返回可預(yù)測(cè)的結(jié)果牍帚,那么python為您提供了將結(jié)果緩存到內(nèi)存中的選項(xiàng)。返回相同結(jié)果的后續(xù)調(diào)用幾乎會(huì)立即返回乳蛾。
各種各樣的例子展示了如何做到這一點(diǎn)暗赶;我最喜歡的記憶幾乎是最少的。python的一個(gè)本機(jī)庫(kù)functools具有@functools.lru緩存裝飾器肃叶,它緩存最近對(duì)函數(shù)的n個(gè)調(diào)用蹂随。當(dāng)正在緩存的值發(fā)生變化,但在特定的時(shí)間窗口內(nèi)是相對(duì)靜態(tài)的時(shí)因惭,這很方便岳锁。一天中最新使用的項(xiàng)目列表就是一個(gè)很好的例子。
2蹦魔。將數(shù)學(xué)移動(dòng)到numpy
如果您正在進(jìn)行基于矩陣或數(shù)組的數(shù)學(xué)運(yùn)算激率,并且不希望Python解釋器妨礙您的工作,請(qǐng)使用numpy勿决。通過(guò)利用C庫(kù)進(jìn)行繁重的工作乒躺,numpy提供了比本機(jī)python更快的數(shù)組處理。它還比Python的內(nèi)置數(shù)據(jù)結(jié)構(gòu)更有效地存儲(chǔ)數(shù)字?jǐn)?shù)據(jù)剥险。
相對(duì)來(lái)說(shuō)聪蘸,非異類(lèi)的數(shù)學(xué)也可以被numpy大大加快。該包為許多常見(jiàn)的python數(shù)學(xué)操作(如min和max)提供了替換表制,這些操作的速度比原來(lái)的python快很多倍健爬。
numpy的另一個(gè)好處是更有效地使用大型對(duì)象的內(nèi)存,比如擁有數(shù)百萬(wàn)個(gè)項(xiàng)目的列表么介。一般來(lái)說(shuō)娜遵,像numpy中那樣的大型對(duì)象如果用傳統(tǒng)的python來(lái)表示,則占用大約四分之一的內(nèi)存壤短。請(qǐng)注意设拟,它有助于從作業(yè)的正確數(shù)據(jù)結(jié)構(gòu)開(kāi)始,即優(yōu)化本身久脯。
重寫(xiě)python算法以使用numpy需要一些工作纳胧,因?yàn)閿?shù)組對(duì)象需要使用numpy的語(yǔ)法聲明。但是numpy在實(shí)際的數(shù)學(xué)運(yùn)算中使用了python現(xiàn)有的習(xí)語(yǔ)(+帘撰、-跑慕,等等),所以從長(zhǎng)遠(yuǎn)來(lái)看,切換到numpy并不會(huì)太令人迷惑核行。
3牢硅。使用C庫(kù)
numpy使用C語(yǔ)言編寫(xiě)的庫(kù)是一種很好的模擬策略。如果有一個(gè)現(xiàn)有的C庫(kù)可以滿足您的需要芝雪,那么Python及其生態(tài)系統(tǒng)提供了幾個(gè)選項(xiàng)來(lái)連接到該庫(kù)并利用其速度减余。
最常見(jiàn)的方法是使用Python的CTypes庫(kù)。因?yàn)閏types與其他python應(yīng)用程序(和運(yùn)行時(shí))廣泛兼容惩系,所以它是最佳的開(kāi)始位置位岔,但它遠(yuǎn)不是鎮(zhèn)上唯一的游戲。cffi項(xiàng)目為c.cython提供了一個(gè)更優(yōu)雅的接口(見(jiàn)下文)堡牡,也可以用來(lái)包裝外部庫(kù)赃承,盡管代價(jià)是必須學(xué)習(xí)cython的標(biāo)記。
學(xué)習(xí)從來(lái)不是一個(gè)人的事情悴侵,要有個(gè)相互監(jiān)督的伙伴瞧剖,工作需要學(xué)習(xí)python或者有興趣學(xué)習(xí)python的伙伴可以私信回復(fù)小編“學(xué)習(xí)” 獲取資料,一起學(xué)習(xí)
4可免。與多處理并行
傳統(tǒng)的python應(yīng)用程序——那些在cpython中實(shí)現(xiàn)的應(yīng)用程序——一次只執(zhí)行一個(gè)線程抓于,以避免在使用多個(gè)線程時(shí)出現(xiàn)狀態(tài)問(wèn)題。這是臭名昭著的全球口譯員鎖(gil)浇借。它的存在有充分的理由捉撮,這并沒(méi)有使它變得更加華麗。
隨著時(shí)間的推移妇垢,gil的效率顯著提高(運(yùn)行python 3而不是python 2的另一個(gè)原因)巾遭,但核心問(wèn)題仍然存在。一個(gè)cpython應(yīng)用程序可以是多線程的闯估,但是cpython不允許這些線程在多個(gè)核心上并行運(yùn)行灼舍。
為了解決這個(gè)問(wèn)題,python提供了多處理模塊來(lái)在不同的核心上運(yùn)行python解釋器的多個(gè)實(shí)例涨薪。狀態(tài)可以通過(guò)共享內(nèi)存或服務(wù)器進(jìn)程來(lái)共享骑素,數(shù)據(jù)可以通過(guò)隊(duì)列或管道在進(jìn)程實(shí)例之間傳遞。
您仍然需要在進(jìn)程之間手動(dòng)管理狀態(tài)刚夺。另外献丑,在啟動(dòng)多個(gè)Python實(shí)例并在其中傳遞對(duì)象的過(guò)程中,不會(huì)有太多的開(kāi)銷(xiāo)侠姑。但是對(duì)于長(zhǎng)期運(yùn)行的進(jìn)程來(lái)說(shuō)创橄,多處理庫(kù)是非常有用的,因?yàn)樗梢詮目绾诵牡牟⑿行灾蝎@益莽红。
另外妥畏,使用C庫(kù)的python模塊和包(如numpy)完全避免使用gil。這也是他們被推薦提速的另一個(gè)原因。
5咖熟。知道你的庫(kù)在做什么
簡(jiǎn)單地輸入XYZ并利用無(wú)數(shù)其他程序員的工作是多么方便!但是您需要知道柳畔,第三方庫(kù)可以改變應(yīng)用程序的性能馍管,而不是總是為了更好。
有時(shí)薪韩,這以明顯的方式表現(xiàn)出來(lái)确沸,例如當(dāng)來(lái)自特定庫(kù)的模塊構(gòu)成瓶頸時(shí)。(再次強(qiáng)調(diào)俘陷,剖析會(huì)有所幫助罗捎。)有時(shí)情況不那么明顯。示例:Pyglet是一個(gè)用于創(chuàng)建窗口化圖形應(yīng)用程序的簡(jiǎn)便庫(kù)拉盾,它自動(dòng)啟用調(diào)試模式桨菜,這會(huì)顯著影響性能,直到顯式禁用它為止捉偏。除非您閱讀文檔倒得,否則您可能永遠(yuǎn)不會(huì)意識(shí)到這一點(diǎn)。閱讀并被告知夭禽。
6霞掺。注意平臺(tái)
python運(yùn)行跨平臺(tái),但這并不意味著每個(gè)操作系統(tǒng)(windows讹躯、linux菩彬、os x)的特性都是在python下抽象出來(lái)的。大多數(shù)時(shí)候潮梯,這意味著要了解平臺(tái)的具體情況骗灶,比如路徑命名約定,對(duì)于這些約定秉馏,有助手函數(shù)矿卑。
但在性能方面,了解平臺(tái)差異也很重要沃饶。例如母廷,在Windows上,需要計(jì)時(shí)器精度小于15毫秒(例如糊肤,對(duì)于多媒體)的python腳本將需要使用Windows API調(diào)用來(lái)訪問(wèn)高分辨率計(jì)時(shí)器或提高計(jì)時(shí)器分辨率琴昆。
7。與PyPy同行
cpython是Python最常用的實(shí)現(xiàn)馆揉,它將兼容性優(yōu)先于原始速度业舍。對(duì)于那些想把速度放在首位的程序員來(lái)說(shuō),有pypy,一個(gè)配備了jit編譯器的python實(shí)現(xiàn)來(lái)加速代碼執(zhí)行舷暮。
因?yàn)閜ypy是作為cpython的替代品而設(shè)計(jì)的态罪,所以它是快速提高性能的最簡(jiǎn)單方法之一。許多常見(jiàn)的python應(yīng)用程序?qū)⒃趐ypy上運(yùn)行下面。一般來(lái)說(shuō)复颈,應(yīng)用程序越依賴于“普通”的python,就越有可能在pypy上運(yùn)行而不進(jìn)行修改沥割。
然而耗啦,充分利用PYPY可能需要測(cè)試和研究。您會(huì)發(fā)現(xiàn)長(zhǎng)時(shí)間運(yùn)行的應(yīng)用程序從Pypy獲得最大的性能收益机杜,因?yàn)榫幾g器會(huì)分析一段時(shí)間后的執(zhí)行情況帜讲。對(duì)于運(yùn)行和退出的短腳本,您最好使用cpython椒拗,因?yàn)樾阅芴嵘蛔阋钥朔﨡IT的開(kāi)銷(xiāo)似将。
請(qǐng)注意,pypy對(duì)python 3的支持仍然落后于幾個(gè)版本蚀苛;它目前支持python 3.2.5玩郊。使用最新的python特性(如async和await-co例程)的代碼將無(wú)法工作。最后枉阵,使用ctypes的python應(yīng)用程序的行為可能并不總是如預(yù)期的那樣译红。如果您正在編寫(xiě)可能同時(shí)在pypy和cpython上運(yùn)行的東西,那么為每個(gè)解釋器分別處理用例可能是有意義的兴溜。
其他通過(guò)抖動(dòng)加速python的實(shí)驗(yàn)仍在取得成果侦厚。其中包括一個(gè)微軟項(xiàng)目pyjion,它為cpython提供了一個(gè)JIT接口拙徽。微軟提供了自己的JIT作為概念證明刨沦。
8。升級(jí)至python 3
如果您使用的是python 2.x膘怕,并且沒(méi)有覆蓋的原因(比如不兼容的模塊)來(lái)堅(jiān)持使用它想诅,那么您應(yīng)該跳到python 3。
除了python 3作為語(yǔ)言的未來(lái)岛心,python 3中還提供了許多構(gòu)造和優(yōu)化来破,而python 2.x中沒(méi)有這些構(gòu)造和優(yōu)化。例如忘古,python 3.5通過(guò)將async和wait關(guān)鍵字作為語(yǔ)言語(yǔ)法的一部分來(lái)減少異步編程的麻煩徘禁。python 3.2對(duì)全局解釋器鎖進(jìn)行了重大升級(jí),顯著改進(jìn)了python處理多個(gè)線程的方式髓堪。