<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          超干分享!如何提高 Python 的運(yùn)行速度?

          共 2767字,需瀏覽 6分鐘

           ·

          2020-12-08 14:05

          點(diǎn)擊上方 “AirPython”,選擇 “加為星標(biāo)
          第一時(shí)間關(guān)注 Python 技術(shù)干貨!


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


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


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


          01
          時(shí)序分析



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


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


          # slow_program.py

          from decimal import *
          def exp(x):
          ????getcontext().prec += 2
          ????i, lasts, s, fact, num = 0, 0, 1, 1, 1
          ????while?s != lasts:
          ????????lasts = s
          ????????i += 1
          ????????fact *= i
          ????????num *= x
          ????????s += num / fact
          ????getcontext().prec -= 2
          ????return?+s

          exp(Decimal(150))
          exp(Decimal(400))
          exp(Decimal(3000))


          最簡約的“配置文件”


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


          ~ $ time python3.8?slow_program.py

          real??0m11,058s
          user 0m11,050s
          sys 0m0,008s


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


          最詳細(xì)的分析


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


          ~ $ python3.8?-m?cProfile -s time slow_program.py

          ?????????1297?function?calls?(1272 primitive calls)?in?11.081 seconds

          ???Ordered by: internal time

          ???ncalls tottime percall cumtime percall filename:lineno(function)
          ????????3???11.079????3.693???11.079????3.693?slow_program.py:4(exp)
          ????????1????0.000????0.000????0.002????0.002?{built-in method _imp.create_dynamic}
          ??????4/1????0.000????0.000???11.081???11.081?{built-in method builtins.exec}
          ????????6????0.000????0.000????0.000????0.000?{built-in method __new__ of type?object at 0x9d12c0}
          ????????6????0.000????0.000????0.000????0.000?abc.py:132(__new__)
          ???????23????0.000????0.000????0.000????0.000?_weakrefset.py:36(__init__)
          ??????245????0.000????0.000????0.000????0.000?{built-in method builtins.getattr}
          ????????2????0.000????0.000????0.000????0.000?{built-in method marshal.loads}
          ???????10????0.000????0.000????0.000????0.000?:1233(find_spec)
          ??????8/4????0.000????0.000????0.000????0.000?abc.py:196(__subclasscheck__)
          ???????15????0.000????0.000????0.000????0.000?{built-in method posix.stat}
          ????????6????0.000????0.000????0.000????0.000?{built-in method builtins.__build_class__}
          ????????1????0.000????0.000????0.000????0.000?__init__.py:357(namedtuple)
          ???????48????0.000????0.000????0.000????0.000?:57(_path_join)
          ???????48????0.000????0.000????0.000????0.000?:59()
          ????????1????0.000????0.000???11.081???11.081?slow_program.py:1()


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


          時(shí)序特定功能


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


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


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


          @timeit_wrapper

          def exp(x):
          ????...

          print('{0:<10} {1:<8} {2:^8}'.format('module', 'function', 'time'))
          exp(Decimal(150))
          exp(Decimal(400))
          exp(Decimal(3000))


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


          ~ $ python3.8?slow_program.py
          module function???time??
          __main__ .exp??????: 0.003267502994276583
          __main__ .exp??????: 0.038535295985639095
          __main__ .exp??????: 11.728486061969306


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


          02
          加速吧!



          讓Python程序運(yùn)行得更快,這部分會(huì)很有趣!我不會(huì)展示可以解決你的性能問題的技巧和代碼,更多地是關(guān)于構(gòu)想和策略的,這些構(gòu)想和策略在使用時(shí)可能會(huì)對(duì)性能產(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í)我們的速度實(shí)在無法與之匹敵。


          使用lru_cache緩存/記憶


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


          import?functools
          import?time
          # caching up to 12 different results
          @functools.lru_cache(maxsize=12)
          def?slow_func(x):
          ????time.sleep(2) # Simulate long computation
          ????return?x

          slow_func(1) # ... waiting for 2 sec before getting result
          slow_func(1) # already cached - result returned instantaneously!
          slow_func(3) # ... waiting for 2 sec before getting result


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


          使用局部變量


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


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


          # Example #1
          class?FastClass:
          ????def?do_stuff(self):
          ????????temp = self.value # this speeds up lookup in loop
          ????????for?i in?range(10000):
          ????????????... # Do something with `temp` here

          # Example #2
          import?random
          def?fast_function():
          ????r = random.random
          ????for?i in?range(10000):
          ????????print(r()) # calling `r()` here, is faster than global random.random()


          使用函數(shù)


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


          def?main():

          ????... # All your previously global code

          main()


          不訪問屬性


          可能會(huì)使你的程序變慢的另一件事是點(diǎn)運(yùn)算符(.),它在獲得對(duì)象屬性時(shí)被使用。此運(yùn)算符使用__getattribute__觸發(fā)字典查找,這會(huì)在代碼中產(chǎn)生額外的開銷。那么,我們?nèi)绾尾拍苷嬲苊猓ㄏ拗疲┦褂盟兀?/span>


          # Slow:
          import?re
          def?slow_func():
          ????for?i in?range(10000):
          ????????re.findall(regex, line) # Slow!

          # Fast:
          from?re import?findall
          def?fast_func():
          ????for?i in?range(10000):
          ????????findall(regex, line) # Faster!


          當(dāng)心字符串


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


          f'{s}?{t}'??# Fast!
          s + ' '?+ t
          ' '.join((s, t))
          '%s %s'?% (s, t)
          '{} {}'.format(s, t)
          Template('$s $t').substitute(s=s, t=t) # Slow!


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


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

          03
          結(jié)論



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


          原文鏈接:

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






          推薦閱讀


          帶你用 Python 實(shí)現(xiàn)自動(dòng)化群控(入門篇)

          這些自動(dòng)化場景,批處理完全可以取代 Python

          我用幾行 Python 自動(dòng)化腳本完美解決掉了小姐姐的微信焦慮感



          得本文對(duì)你有幫助?請(qǐng)分享給更多人

          瀏覽 74
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  色婷婷在线视频观看 | 日日摸日日添日日躁AV | 色五月婷婷在线 | 九一无码视频 | 免费观看黄a一级视频 |