自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

代碼跑得慢甩鍋Python?手把手教你如何給代碼提速30%

開發(fā) 開發(fā)工具 后端
Python已經(jīng)得到了全球程序員的喜愛,但是還是遭到一些人的詬病,原因之一就是認(rèn)為它運(yùn)行緩慢。Medium上一位小哥就詳細(xì)講了講如何讓python提速30%,以此證明代碼跑得慢不是python的問題,而是代碼本身的問題。

[[312899]]

大數(shù)據(jù)文摘出品

來源:Medium

編譯:王轉(zhuǎn)轉(zhuǎn)

Python已經(jīng)得到了全球程序員的喜愛,但是還是遭到一些人的詬病,原因之一就是認(rèn)為它運(yùn)行緩慢。

其實(shí)某個特定程序(無論使用何種編程語言)的運(yùn)行速度是快還是慢,在很大程度上取決于編寫該程序的開發(fā)人員自身素質(zhì),以及他們編寫優(yōu)化而高效代碼的能力。

Medium上一位小哥就詳細(xì)講了講如何讓python提速30%,以此證明代碼跑得慢不是python的問題,而是代碼本身的問題。

時序分析

在開始進(jìn)行任何優(yōu)化之前,我們首先需要找出代碼的哪些部分使整個程序變慢。有時程序的問題很明顯,但是如果你一時不知道問題出在哪里,那么這里有一些可能的選項(xiàng):

注意:這是我將用于演示的程序,它將進(jìn)行指數(shù)計(jì)算(取自Python文檔):

  1. # slow_program.py 
  2.  
  3. from decimal import * 
  4.  
  5. def exp(x): 
  6.     getcontext().prec += 2 
  7.     i, lasts, s, fact, num = 0, 0, 1, 1, 1 
  8.     while s != lasts: 
  9.         lasts = s 
  10.         i += 1 
  11.         fact *= i 
  12.         num *= x 
  13.         s += num / fact 
  14.     getcontext().prec -2 
  15.     return +s 
  16.  
  17. exp(Decimal(150)) 
  18. exp(Decimal(400)) 
  19. exp(Decimal(3000)) 

最簡約的“配置文件”

首先,最簡單最偷懶的方法——Unix時間命令。

  1. ~ $ time python3.8 slow_program.py 
  2.  
  3. real  0m11,058s 
  4. user 0m11,050s 
  5. sys 0m0,008s 

如果你只能直到整個程序的運(yùn)行時間,這樣就夠了,但通常這還遠(yuǎn)遠(yuǎn)不夠。

最詳細(xì)的分析

另外一個指令是cProfile,但是它提供的信息過于詳細(xì)了。

  1. ~ $ python3.8 -m cProfile -s time slow_program.py 
  2.  
  3.          1297 function calls (1272 primitive calls) in 11.081 seconds 
  4.  
  5.    Ordered by: internal time 
  6.  
  7.    ncalls tottime percall cumtime percall filename:lineno(function) 
  8.         3   11.079    3.693   11.079    3.693 slow_program.py:4(exp) 
  9.         1    0.000    0.000    0.002    0.002 {built-in method _imp.create_dynamic} 
  10.       4/1    0.000    0.000   11.081   11.081 {built-in method builtins.exec} 
  11.         6    0.000    0.000    0.000    0.000 {built-in method __new__ of type object at 0x9d12c0} 
  12.         6    0.000    0.000    0.000    0.000 abc.py:132(__new__) 
  13.        23    0.000    0.000    0.000    0.000 _weakrefset.py:36(__init__) 
  14.       245    0.000    0.000    0.000    0.000 {built-in method builtins.getattr} 
  15.         2    0.000    0.000    0.000    0.000 {built-in method marshal.loads} 
  16.        10    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:1233(find_spec) 
  17.       8/4    0.000    0.000    0.000    0.000 abc.py:196(__subclasscheck__) 
  18.        15    0.000    0.000    0.000    0.000 {built-in method posix.stat} 
  19.         6    0.000    0.000    0.000    0.000 {built-in method builtins.__build_class__} 
  20.         1    0.000    0.000    0.000    0.000 __init__.py:357(namedtuple) 
  21.        48    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:57(_path_join) 
  22.        48    0.000    0.000    0.000    0.000 <frozen importlib._bootstrap_external>:59(<listcomp>
  23.         1    0.000    0.000   11.081   11.081 slow_program.py:1(<module>

在這里,我們使用cProfile模塊和time參數(shù)運(yùn)行測試腳本,以便按內(nèi)部時間(cumtime)對行進(jìn)行排序。這給了我們很多信息,你在上面看到的行大約是實(shí)際輸出的10%。由此可見,exp函數(shù)是罪魁禍?zhǔn)祝F(xiàn)在我們可以更詳細(xì)地了解時序和性能分析。

時序特定功能

現(xiàn)在我們知道了應(yīng)當(dāng)主要關(guān)注哪里,我們可能想對運(yùn)行速度緩慢的函數(shù)計(jì)時,而不用測量其余的代碼。為此,我們可以使用一個簡單的裝飾器:

  1. def timeit_wrapper(func): 
  2.     @wraps(func) 
  3.     def wrapper(*args, **kwargs): 
  4.         start = time.perf_counter() # Alternatively, you can use time.process_time() 
  5.         funcfunc_return_val = func(*args, **kwargs) 
  6.         end = time.perf_counter() 
  7.         print('{0:<10}.{1:<8} : {2:<8}'.format(func.__module__, func.__name__, end - start)) 
  8.         return func_return_val 
  9.     return wrapper 

然后可以將此裝飾器應(yīng)用于待測功能,如下所示:

  1. @timeit_wrapper 
  2.  
  3. def exp(x): 
  4.     ... 
  5.  
  6. print('{0:<10} {1:<8} {2:^8}'.format('module', 'function', 'time')) 
  7. exp(Decimal(150)) 
  8. exp(Decimal(400)) 
  9. exp(Decimal(3000)) 

這給出我們?nèi)缦螺敵觯?/p>

  1. ~ $ python3.8 slow_program.py 
  2. module function   time   
  3. __main__ .exp      : 0.003267502994276583 
  4. __main__ .exp      : 0.038535295985639095 
  5. __main__ .exp      : 11.728486061969306 

需要考慮的一件事是我們實(shí)際想要測量的時間。時間包提供time.perf_counter和time.process_time兩個函數(shù)。他們的區(qū)別在于perf_counter返回的絕對值,包括你的Python程序進(jìn)程未運(yùn)行時的時間,因此它可能會受到計(jì)算機(jī)負(fù)載的影響。另一方面,process_time僅返回用戶時間(不包括系統(tǒng)時間),這僅是你的過程時間。

加速吧!

讓Python程序運(yùn)行得更快,這部分會很有趣!我不會展示可以解決你的性能問題的技巧和代碼,更多地是關(guān)于構(gòu)想和策略的,這些構(gòu)想和策略在使用時可能會對性能產(chǎn)生巨大影響,在某些情況下,可以將速度提高30%。

使用內(nèi)置數(shù)據(jù)類型

這一點(diǎn)很明顯。內(nèi)置數(shù)據(jù)類型非???,尤其是與我們的自定義類型(例如樹或鏈接列表)相比。這主要是因?yàn)閮?nèi)置程序是用C實(shí)現(xiàn)的,因此在使用Python進(jìn)行編碼時我們的速度實(shí)在無法與之匹敵。

使用lru_cache緩存/記憶

我已經(jīng)在上一篇博客中展示了此內(nèi)容,但我認(rèn)為值得用簡單的示例來重復(fù)它:

  1. import functools 
  2. import time 
  3. # caching up to 12 different results 
  4. @functools.lru_cache(maxsize=12
  5. def slow_func(x): 
  6.     time.sleep(2) # Simulate long computation 
  7.     return x 
  8.  
  9. slow_func(1) # ... waiting for 2 sec before getting result 
  10. slow_func(1) # already cached - result returned instantaneously! 
  11. slow_func(3) # ... waiting for 2 sec before getting result 

上面的函數(shù)使用time.sleep模擬大量計(jì)算。第一次使用參數(shù)1調(diào)用時,它將等待2秒鐘,然后才返回結(jié)果。再次調(diào)用時,結(jié)果已經(jīng)被緩存,因此它將跳過函數(shù)的主體并立即返回結(jié)果。有關(guān)更多實(shí)際示例,請參見以前的博客文章。

使用局部變量

這與在每個作用域中查找變量的速度有關(guān),因?yàn)樗恢皇鞘褂镁植孔兞窟€是全局變量。實(shí)際上,即使在函數(shù)的局部變量(最快),類級屬性(例如self.name——較慢)和全局(例如,導(dǎo)入的函數(shù))如time.time(最慢)之間,查找速度實(shí)際上也有所不同。

你可以通過使用看似不必要的分配來提高性能,如下所示:

  1. # Example #1 
  2. class FastClass: 
  3.     def do_stuff(self): 
  4.         temp = self.value # this speeds up lookup in loop 
  5.         for i in range(10000): 
  6.             ... # Do something with `temp` here 
  7.  
  8. # Example #2 
  9. import random 
  10. def fast_function(): 
  11.     r = random.random 
  12.     for i in range(10000): 
  13.         print(r()) # calling `r()` here, is faster than global random.random() 

使用函數(shù)

這似乎違反直覺,因?yàn)檎{(diào)用函數(shù)會將更多的東西放到堆棧上,并從函數(shù)返回中產(chǎn)生開銷,但這與上一點(diǎn)有關(guān)。如果僅將整個代碼放在一個文件中而不將其放入函數(shù)中,則由于全局變量,它的運(yùn)行速度會慢得多。因此,你可以通過將整個代碼包裝在main函數(shù)中并調(diào)用一次來加速代碼,如下所示:

  1. def main(): 
  2.  
  3.     ... # All your previously global code 
  4.  
  5. main() 

不訪問屬性

可能會使你的程序變慢的另一件事是點(diǎn)運(yùn)算符(.),它在獲得對象屬性時被使用。此運(yùn)算符使用__getattribute__觸發(fā)字典查找,這會在代碼中產(chǎn)生額外的開銷。那么,我們?nèi)绾尾拍苷嬲苊?限制)使用它呢?

  1. # Slow: 
  2. import re 
  3. def slow_func(): 
  4.     for i in range(10000): 
  5.         re.findall(regex, line) # Slow! 
  6.  
  7. # Fast: 
  8. from re import findall 
  9. def fast_func(): 
  10.     for i in range(10000): 
  11.         findall(regex, line) # Faster! 

當(dāng)心字符串

使用模數(shù)(%s)或.format()進(jìn)行循環(huán)運(yùn)行時,字符串操作可能會變得非常慢。我們有什么更好的選擇?根據(jù)雷蒙德·海廷格(Raymond Hettinger)最近的推特,我們唯一應(yīng)該使用的是f字符串,它是最易讀,最簡潔且最快的方法。根據(jù)該推特,這是你可以使用的方法列表——最快到最慢:

  1. f'{s} {t}'  # Fast! 
  2. s + ' ' + t 
  3. ' '.join((s, t)) 
  4. '%s %s' % (s, t) 
  5. '{} {}'.format(s, t) 
  6. Template('$s $t').substitute(ss=s, tt=t) # Slow! 

生成器本質(zhì)上并沒有更快,因?yàn)樗鼈儽辉试S進(jìn)行延遲計(jì)算,從而節(jié)省了內(nèi)存而不是時間。但是,保存的內(nèi)存可能會導(dǎo)致你的程序?qū)嶋H運(yùn)行得更快。這是怎么做到的?如果你有一個很大的數(shù)據(jù)集,而沒有使用生成器(迭代器),那么數(shù)據(jù)可能會溢出CPU L1緩存,這將大大減慢內(nèi)存中值的查找速度。

在性能方面,非常重要的一點(diǎn)是CPU可以將正在處理的所有數(shù)據(jù)盡可能地保存在緩存中。你可以觀看Raymond Hettingers的視頻,他在其中提到了這些問題。

結(jié)論

優(yōu)化的首要規(guī)則是不要優(yōu)化。但是,如果確實(shí)需要,那么我希望上面這些技巧可以幫助你。但是,在優(yōu)化代碼時要小心,因?yàn)樗赡茏罱K使你的代碼難以閱讀,因此難以維護(hù),這可能超過優(yōu)化的好處。

相關(guān)報(bào)道:

https://towardsdatascience.com/making-python-programs-blazingly-fast-c1cd79bd1b32

【本文是51CTO專欄機(jī)構(gòu)大數(shù)據(jù)文摘的原創(chuàng)譯文,微信公眾號“大數(shù)據(jù)文摘( id: BigDataDigest)”】

     大數(shù)據(jù)文摘二維碼

戳這里,看該作者更多好文

 

責(zé)任編輯:趙寧寧 來源: 51CTO專欄
相關(guān)推薦

2020-08-12 09:07:53

Python開發(fā)爬蟲

2021-09-26 16:08:23

CC++clang_forma

2021-11-09 06:55:03

水印圖像開發(fā)

2024-01-26 08:16:48

Exporter開源cprobe

2023-09-16 18:48:28

代碼邏輯

2020-08-12 07:41:39

SQL 優(yōu)化語句

2021-11-24 16:02:57

鴻蒙HarmonyOS應(yīng)用

2022-12-07 08:42:35

2022-07-27 08:16:22

搜索引擎Lucene

2021-07-13 10:17:25

GitHubLinux代碼

2010-04-29 09:49:26

代碼提示SQL Server

2022-01-10 11:52:46

Gitee服務(wù)器代碼

2021-07-14 09:00:00

JavaFX開發(fā)應(yīng)用

2011-01-10 14:41:26

2011-05-03 15:59:00

黑盒打印機(jī)

2020-03-04 09:25:39

Python網(wǎng)絡(luò)安全WiFi

2011-04-25 10:26:54

打印機(jī)

2017-10-29 21:43:25

人臉識別

2022-01-09 20:26:14

Flink源代碼編譯

2013-07-05 10:16:32

程序員
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號