讓你的Python提速30%!(下)

  • 2020 年 2 月 23 日
  • 筆記

編輯 | sunlei 發布 | ATYUN訂閱號

前文回顧:讓你的Python提速30%!(上)

使它更快

現在進入有趣的部分。讓我們幫您的Python程式運行得更快。我(基本上)不會向您展示一些能夠神奇地解決性能問題的黑客、技巧和程式碼片段。這更多的是關於一般的想法和策略,當使用時,它們可以對性能產生巨大的影響,在某些情況下可以提高30%的速度。

使用內置數據類型

這一點很明顯。內置數據類型非常快,特別是與我們的自定義類型(如樹或鏈接列表)相比。這主要是因為內置程式碼是用C實現的,在用Python編寫程式碼時,我們在速度上無法真正匹配。

快取/記憶lru_cache

用一個簡單的例子來重複一下:

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

上面的函數使用time.sleep模擬繁重的計算。當第一次用參數1調用時,它等待2秒,然後才返回結果。再次調用時,結果已被快取,因此它跳過函數體並立即返回結果。

使用局部變數

這與在每個作用域中查找變數的速度有關。我編寫每個作用域,因為它不僅僅是使用局部變數和全局變數。實際上,查找速度甚至在函數中的局部變數(最快)、類級屬性(例如self.name-slower)和全局(例如time.time(最慢))之間也存在差異。

你可以通過使用看起來不必要的(直接的無用的)任務來提高性能,比如:

#  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()

使用函數

這似乎有悖常理,因為調用函數會將更多的東西放到堆棧中,並從函數返回中產生開銷,但這與前面的一點有關。如果只將整個程式碼放在一個文件中而不將其放在函數中,則會因為全局變數而慢得多。因此,只需將整個程式碼包裝在main函數中並調用一次,就可以加快程式碼的速度,如下所示:

def main():      ...  # All your previously global code    main()

不訪問屬性

另一個可能會減慢程式速度的是點運算符(.),它在訪問對象屬性時使用。此運算符使用_getattribute__觸發字典查找,這會在程式碼中產生額外的開銷。那麼,我們如何才能真正避免(限制)使用它呢?

#  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!

謹防字元串

在循環中使用例如module(%s)或.format()運行時,對字元串的操作可能會非常慢。我們還有什麼更好的選擇?根據Raymond Hettinger最近的推文,我們唯一應該使用的是f-string,它是最可讀、最簡潔、最快的方法。因此,根據這條推文,這是你可以使用的方法列表-從最快到最慢:

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!

生成器本身並不是更快的,因為它們允許延遲計算,這節省了記憶體而不是時間。但是,節省的記憶體可能會導致程式實際上運行得更快。怎樣?好吧,如果您有一個大型數據集,並且不使用生成器(迭代器),那麼數據可能會溢出cpu L1快取,這將顯著減慢在記憶體中查找值的速度。

說到性能,很重要的一點是CPU可以保存它正在處理的所有數據,儘可能接近地保存在快取中。你可以看Raymond Hettingers的演講,他提到了這些問題。

結論

優化的第一條規則是不要這樣做。但是,如果你真的需要的話,我希望這幾條建議能幫到你。但是,在優化程式碼時要小心,因為它可能會導致程式碼難以閱讀,因此難以維護,這可能會超過優化的好處。

原文鏈接:

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