python中if的效率_Python 代码性能优化技巧
選擇了腳本語言就要忍受其速度,這句話在某種程度上說明了 python 作為腳本的一個不足之處,那就是執(zhí)行效率和性能不夠理想,特別是在 performance 較差的機器上,因此有必要進(jìn)行一定的代碼優(yōu)化來提高程序的執(zhí)行效率。
Python為什么性能差?
1、python是動態(tài)語言
一個變量所指向?qū)ο蟮念愋驮谶\行時才確定,編譯器做不了任何預(yù)測,也就無從優(yōu)化。舉一個簡單的例子: r = a + b?!和b相加,但a和b的類型在運行時才知道,對于加法操作,不同的類型有不同的處理,所以每次運行的時候都會去判斷a和b的類型,然后執(zhí)行對應(yīng)的操作。而在靜態(tài)語言如C++中,編譯的時候就確定了運行時的代碼。
另外一個例子是屬性查找,關(guān)于具體的查找順序在《python屬性查找》中有詳細(xì)介紹。簡而言之,訪問對象的某個屬性是一個非常復(fù)雜的過程,而且通過同一個變量訪問到的python對象還都可能不一樣(參見Lazy property的例子)。而在C語言中,訪問屬性用對象的地址加上屬性的偏移就可以了。
2、python是解釋執(zhí)行
Python不支持JIT(just in time compiler)。雖然大名鼎鼎的google曾經(jīng)嘗試Unladen Swallow 這個項目,但最終也折了。
3、python中一切都是對象
每個對象都需要維護(hù)引用計數(shù),增加了額外的工作。
4、python GIL
GIL是Python最為詬病的一點,因為GIL,python中的多線程并不能真正的并發(fā)。如果是在IO bound的業(yè)務(wù)場景,這個問題并不大,但是在CPU BOUND的場景,這就很致命了。所以筆者在工作中使用python多線程的情況并不多,一般都是使用多進(jìn)程(pre fork),或者在加上協(xié)程。即使在單線程,GIL也會帶來很大的性能影響,因為python每執(zhí)行100個opcode(默認(rèn),可以通過sys.setcheckinterval()設(shè)置)就會嘗試線程的切換,具體的源代碼在ceval.c::PyEval_EvalFrameEx。
5、垃圾回收
這個可能是所有具有垃圾回收的編程語言的通病。python采用標(biāo)記和分代的垃圾回收策略,每次垃圾回收的時候都會中斷正在執(zhí)行的程序,造成所謂的頓卡。infoq上有一篇文章,提到禁用Python的GC機制后,Instagram性能提升了10%。
Python代碼優(yōu)化常用技巧
減小代碼體積
提高代碼的運行效率
一、改進(jìn)算法,選擇合適的數(shù)據(jù)結(jié)構(gòu)(更好的選擇已經(jīng)用藍(lán)色字標(biāo)注出來了)
在算法的時間復(fù)雜度排序上依次是:
O(1) -> O(lg n) -> O(n lg n) -> O(n^2) -> O(n^3) -> O(n^k) -> O(k^n) -> O(n!)
當(dāng)然選擇更合理的算法是最好的優(yōu)化手段,但是在算法沒有辦法更加合理化的時候我們就要選擇更好的數(shù)據(jù)結(jié)構(gòu)。
1、字典(dictionary)與列表(list)
Python 字典中使用了 hash table,因此查找操作的復(fù)雜度為 O(1),而 list 實際是個數(shù)組,在 list 中,查找需要遍歷整個 list,其復(fù)雜度為 O(n),因此對成員的查找訪問等操作字典要比 list 更快。
使用字典會比使用列表效率大概提高一半。
因此在需要多數(shù)據(jù)成員進(jìn)行頻繁的查找或者訪問的時候,使用 dict 而不是 list 是一個較好的選擇。
2、集合(set)和列表(list)
set 的 union, intersection,difference 操作要比 list 的迭代要快。因此如果涉及到求 list 交集,并集或者差的問題可以轉(zhuǎn)換為 set 來操作。(附表一)
表 1. set 常見用法
語法
操作
說明
set(list1) | set(list2)
union
包含 list1 和 list2 所有數(shù)據(jù)的新集合
set(list1) & set(list2)
intersection
包含 list1 和 list2 中共同元素的新集合
set(list1) - set(list2)
difference
在 list1 中出現(xiàn)但不在 list2 中出現(xiàn)的元素的集合
3、對循環(huán)的優(yōu)化
對循環(huán)的優(yōu)化所遵循的原則是盡量減少循環(huán)過程中的計算量,有多重循環(huán)的盡量將內(nèi)層的計算提到上一層。
4、充分利用Lazy if-evaluation 的特性
python 中條件表達(dá)式是 lazy evaluation 的,也就是說如果存在條件表達(dá)式 if x and y,在 x 為 false 的情況下 y 表達(dá)式的值將不再計算。
所以在保證不改變運行結(jié)果的前提下,盡量減少IF里面的條件來提高程序的效率
5、字符串的優(yōu)化
在字符串連接的使用盡量使用 join() 而不是 +;
當(dāng)對字符串可以使用正則表達(dá)式或者內(nèi)置函數(shù)來處理的時候,選擇內(nèi)置函數(shù)。如 str.isalpha(),str.isdigit(),str.startswith(('x', 'yz')),str.endswith(('x', 'yz'));
對字符進(jìn)行格式化比直接串聯(lián)讀取要快,因此要使用
1 out = "%s%s%s%s" % (head, prologue, query, tail)
而不是
1 out = "" + head + prologue + query + tail + ""
6、使用列表解析(list comprehension)和生成器表達(dá)式(generator expression)
列表解析要比在循環(huán)中重新構(gòu)建一個新的 list 更為高效,因此我們可以利用這一特性來提高運行的效率。
1 for i in range (1000000):2 for w inlist:3 total.append(w)
使用列表解析:
1 for i in range (1000000):2 a = [w for w in list]
7、其他優(yōu)化技巧
如果需要交換兩個變量的值使用 a,b=b,a而不是借助中間變量 t=a;a=b;b=t;
在循環(huán)的時候使用 xrange 而不是 range;使用 xrange 可以節(jié)省大量的系統(tǒng)內(nèi)存,因為 xrange() 在序列中每次調(diào)用只產(chǎn)生一個整數(shù)元素。而 range() 將直接返回完整的元素列表,用于循環(huán)時會有不必要的開銷;
使用局部變量,避免"global" 關(guān)鍵字。python 訪問局部變量會比全局變量要快得多;
if done is not None比語句 if done != None 更快;
在耗時較多的循環(huán)中,可以把函數(shù)的調(diào)用改為內(nèi)聯(lián)的方式;
使用級聯(lián)比較 "x < y < z" 而不是 "x < y and y < z";
while 1 要比 while True 更快(當(dāng)然后者的可讀性更好);
build in 函數(shù)通常較快,add(a,b) 要優(yōu)于 a+b。
二、定位程序性能瓶頸
使用 profile 進(jìn)行性能分析
其中 Profiler 是 python 自帶的一組程序,能夠描述程序運行時候的性能,并提供各種統(tǒng)計幫助用戶定位程序的性能瓶頸。
profile 的使用非常簡單,只需要在使用之前進(jìn)行 import 即可。具體實例如下:
1 importprofile2 defprofileTest():3 Total =1;4 for i in range(10):5 Total=Total*(i+1)6 printTotal7 returnTotal8 if __name__ == "__main__":9 profile.run("profileTest()")
程序的運行結(jié)果如下:
其中輸出每列的具體解釋如下:
ncalls:表示函數(shù)調(diào)用的次數(shù);
tottime:表示指定函數(shù)的總的運行時間,除掉函數(shù)中調(diào)用子函數(shù)的運行時間;
percall:(第一個 percall)等于 tottime/ncalls;
cumtime:表示該函數(shù)及其所有子函數(shù)的調(diào)用運行的時間,即函數(shù)開始調(diào)用到返回的時間;
percall:(第二個 percall)即函數(shù)運行一次的平均時間,等于 cumtime/ncalls;
filename:lineno(function):每個函數(shù)調(diào)用的具體信息;
三、Python性能優(yōu)化工具
? Python 性能優(yōu)化除了改進(jìn)算法,選用合適的數(shù)據(jù)結(jié)構(gòu)之外,還有幾種關(guān)鍵的技術(shù),比如將關(guān)鍵 python 代碼部分重寫成 C 擴展模塊,或者選用在性能上更為優(yōu)化的解釋器等,這些在本文中統(tǒng)稱為優(yōu)化工具。python 有很多自帶的優(yōu)化工具,如 Pypy,Cython,Pyrex 等,這些優(yōu)化工具各有千秋,本節(jié)選擇幾種進(jìn)行介紹。
1、Pypy
PyPy 表示 "用 Python 實現(xiàn)的 Python",但實際上它是使用一個稱為 RPython 的 Python 子集實現(xiàn)的,能夠?qū)?Python 代碼轉(zhuǎn)成 C, .NET, Java 等語言和平臺的代碼。PyPy 集成了一種即時 (JIT) 編譯器。和許多編譯器,解釋器不同,它不關(guān)心 Python 代碼的詞法分析和語法樹。 因為它是用 Python 語言寫的,所以它直接利用 Python 語言的 Code Object.。 Code Object 是 Python 字節(jié)碼的表示,也就是說, PyPy 直接分析 Python 代碼所對應(yīng)的字節(jié)碼 ,,這些字節(jié)碼即不是以字符形式也不是以某種二進(jìn)制格式保存在文件中, 而在 Python 運行環(huán)境中。目前版本是 1.8. 支持不同的平臺安裝,windows 上安裝 Pypy 需要先下載 ,然后解壓到相關(guān)的目錄,并將解壓后的路徑添加到環(huán)境變量 path 中即可。
接下來我們來測試一下使用同一個程序python解釋器和pypy解釋器的編譯時間為多少?到底有沒有提升速度?
1 importtime2
3 t =time.time()4 for i in range(10 ** 8):5 continue
6 print(time.time() - t)
我們可以看到這是python解釋器的編譯時間:
下面這是pypy解釋器的編譯時間:
從上面對比我們發(fā)現(xiàn)python解釋器的編譯時間為:5.84,
而pypy解釋器的編譯時間為0.32,
從編譯時間上我們可以看出速度提升了將近22倍!!
2、Cython
Cython 是用 python 實現(xiàn)的一種語言,可以用來寫 python 擴展,用它寫出來的庫都可以通過 import 來載入,性能上比 python 的快。cython 里可以載入 python 擴展 ( 比如 import math),也可以載入 c 的庫的頭文件 ( 比如 :cdef extern from "math.h"),另外也可以用它來寫 python 代碼。將關(guān)鍵部分重寫成 C 擴展模塊
Linux Cpython 的安裝可以參考文檔
Cython 代碼與 python 不同,必須先編譯,編譯一般需要經(jīng)過兩個階段,將 pyx 文件編譯為 .c 文件,再將 .c 文件編譯為 .so 文件。編譯有多種方法:
通過命令行編譯
使用 distutils 編譯
下面來進(jìn)行一個簡單的性能比較:
1 from time importtime2 deftest(int n):3 cdef int a =04 cdef int i5 for i inxrange(n):6 a+=i7 returna8
9 t =time()10 test(10000000)11 print "total run time:"
12 print time()-t
測試結(jié)果:
[GCC 4.0.2 20051125 (Red Hat 4.0.2-8)] on linux2
Type"help", "copyright", "credits" or "license" formore information.>>> importpyximport; pyximport.install()>>> importctest
total run time:0.00714015960693
使用python測試結(jié)果:
通過清楚地對比可以發(fā)現(xiàn)使用 Cython 的速度提升了100多倍。
總結(jié)
以上是生活随笔為你收集整理的python中if的效率_Python 代码性能优化技巧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python语言的类型是_Python到
- 下一篇: websocket python爬虫_p