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

2021-01-11 大數據文摘

大數據文摘出品

來源:Medium

編譯:王轉轉

Python已經得到了全球程式設計師的喜愛,但是還是遭到一些人的詬病,原因之一就是認為它運行緩慢。

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

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

時序分析

在開始進行任何優化之前,我們首先需要找出代碼的哪些部分使整個程序變慢。有時程序的問題很明顯,但是如果你一時不知道問題出在哪裡,那麼這裡有一些可能的選項:

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

# slow_program.pyfrom decimal import *defexp(x):getcontext().prec +=2i, lasts, s, fact, num =0,0,1,1,1whiles !=lasts:lasts = si +=1fact *= inum *=xs += num / factgetcontext().prec -=2return+sexp(Decimal(150))exp(Decimal(400))exp(Decimal(3000))

最簡約的「配置文件」

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

~ $ time python3.8slow_program.pyreal0m11,058suser0m11,050ssys0m0,008s

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

最詳細的分析

另外一個指令是cProfile,但是它提供的信息過於詳細了。

~ $python3.8-mcProfile -s time slow_program.py1297functioncalls(1272 primitive calls)in11.081secondsOrdered by: internal timencalls tottime percall cumtime percall filename:lineno(function)311.0793.69311.0793.693slow_program.py:4(exp)10.0000.0000.0020.002{built-in method _imp.create_dynamic}4/10.0000.00011.08111.081{built-in method builtins.exec}60.0000.0000.0000.000{built-in method __new__ oftypeobject at0x9d12c0}60.0000.0000.0000.000abc.py:132(__new__)230.0000.0000.0000.000_weakrefset.py:36(__init__)2450.0000.0000.0000.000{built-in method builtins.getattr}20.0000.0000.0000.000{built-in method marshal.loads}100.0000.0000.0000.000<frozen importlib._bootstrap_external>:1233(find_spec)8/40.0000.0000.0000.000abc.py:196(__subclasscheck__)150.0000.0000.0000.000{built-in method posix.stat}60.0000.0000.0000.000{built-in method builtins.__build_class__}10.0000.0000.0000.000__init__.py:357(namedtuple)480.0000.0000.0000.000<frozen importlib._bootstrap_external>:57(_path_join)480.0000.0000.0000.000<frozen importlib._bootstrap_external>:59(<listcomp>)10.0000.00011.08111.081slow_program.py:1(<module>)

在這裡,我們使用cProfile模塊和time參數運行測試腳本,以便按內部時間(cumtime)對行進行排序。這給了我們很多信息,你在上面看到的行大約是實際輸出的10%。由此可見,exp函數是罪魁禍首,現在我們可以更詳細地了解時序和性能分析。

時序特定功能

現在我們知道了應當主要關注哪裡,我們可能想對運行速度緩慢的函數計時,而不用測量其餘的代碼。為此,我們可以使用一個簡單的裝飾器:

deftimeit_wrapper(func):@wraps(func)defwrapper(*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))returnfunc_return_valreturnwrapper

然後可以將此裝飾器應用於待測功能,如下所示:

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

這給出我們如下輸出:

~ $python3.8slow_program.pymodulefunctiontime__main__ .exp:0.003267502994276583__main__ .exp:0.038535295985639095__main__ .exp:11.728486061969306

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

加速吧!

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

使用內置數據類型

這一點很明顯。內置數據類型非常快,尤其是與我們的自定義類型(例如樹或連結列表)相比。這主要是因為內置程序是用C實現的,因此在使用Python進行編碼時我們的速度實在無法與之匹敵。

使用lru_cache緩存/記憶

我已經在上一篇博客中展示了此內容,但我認為值得用簡單的示例來重複它:

importfunctoolsimporttime# caching up to 12 different results@functools.lru_cache(maxsize=12)defslow_func(x):time.sleep(2)# Simulate long computationreturnxslow_func(1)# ... waiting for 2 sec before getting resultslow_func(1)# already cached - result returned instantaneously!slow_func(3)# ... waiting for 2 sec before getting result

上面的函數使用time.sleep模擬大量計算。第一次使用參數1調用時,它將等待2秒鐘,然後才返回結果。再次調用時,結果已經被緩存,因此它將跳過函數的主體並立即返回結果。有關更多實際示例,請參見以前的博客文章。

使用局部變量

這與在每個作用域中查找變量的速度有關,因為它不只是使用局部變量還是全局變量。實際上,即使在函數的局部變量(最快),類級屬性(例如self.name——較慢)和全局(例如,導入的函數)如time.time(最慢)之間,查找速度實際上也有所不同。

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

# Example #1classFastClass:defdo_stuff(self):temp = self.value# this speeds up lookup in loopforiinrange(10000):...# Do something with `temp` here# Example #2importrandomdeffast_function():r = random.randomforiinrange(10000):print(r())# calling `r()` here, is faster than global random.random()

使用函數

這似乎違反直覺,因為調用函數會將更多的東西放到堆棧上,並從函數返回中產生開銷,但這與上一點有關。如果僅將整個代碼放在一個文件中而不將其放入函數中,則由於全局變量,它的運行速度會慢得多。因此,你可以通過將整個代碼包裝在main函數中並調用一次來加速代碼,如下所示:

defmain():...# All your previously global codemain()

不訪問屬性

可能會使你的程序變慢的另一件事是點運算符(.),它在獲得對象屬性時被使用。此運算符使用__getattribute__觸發字典查找,這會在代碼中產生額外的開銷。那麼,我們如何才能真正避免(限制)使用它呢?

# Slow:importredefslow_func():foriinrange(10000):re.findall(regex, line)# Slow!# Fast:fromreimportfindalldeffast_func():foriinrange(10000):findall(regex, line)# Faster!

當心字符串

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

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

相關焦點

  • Python代碼性能調試和優化
    一直以來Python性能是遭人詬病的問題之一,抱怨執行慢,沒法用。雖然再性能上語言的差異確實存在著明顯差異,但是我認為一個非常流行的語言,運行的快慢不會成為阻擾人們使用的因素。如果是的話,可能是由於編寫的程序有問題,需要優化。本文蟲蟲就給大家介紹一下如何調試Python應用的性能,以及怎麼對其進行優化。
  • 如何在Python中編寫簡單代碼,並且速度超越Spark?
    如果你想在Python中編寫簡單代碼,並且用比Spark更快的速度運行,同時無需重新編碼、無需開發者解決部署、擴展和監控問題,可能嗎?你可能會「說我是一個夢想家」。我是一個夢想家,但不是唯一的一個!本篇文章將證明如今可以使用Nuclio和RAPIDSlimg令以上設想成為現實,它們是由NVIDIA孵化的免費開源數據科學加速平臺。
  • 這些方法,能夠讓你的Python程序快如閃電
    本文將介紹如何提升 Python 程序的效率,讓它們運行飛快!計時與性能分析在開始優化之前,我們首先需要找到代碼的哪一部分真正拖慢了整個程序。讓程序更快現在到了真正有趣的部分了,讓 Python 程序跑得更快!我不會告訴你一些奇技淫巧或代碼段來神奇地解決程序的性能問題,而更多是關於通用的想法和策略。使用這些策略,可以對程序性能產生巨大的影響,有時甚至可以帶來高達 30% 的提速。使用內置的數據類型這一點非常明顯。內置的數據類型非常快,尤其相比於樹或鍊表等自定義類型而言。
  • 華為雲應用編排,手把手教您完成pytorch代碼部署
    本文將以一個使用了pytorch的demo代碼pytorch-classify為例,通過華為雲上的容器服務一鍵式部署, 5 分鐘完成免費的雲上pytorch代碼的部署。傳統部署方式首先是準備環境。先有個伺服器,這臺伺服器需要能夠被外部訪問。
  • 懶人秘籍:教你如何避免編寫pandas代碼
    而大家都在儘可能地避免這種懸崖峭壁,結果可想而知,都轉向了如何避免編寫pandas代碼。在過去4年裡,筆者一直使用pandas作為數據分析的主要工具。必須承認,「如何避免編寫pandas代碼」的大部分內容來自於使用pandas編程的起步階段。在進行代碼審閱時,筆者仍然看到許多經驗豐富的程式設計師在看一些熱門「如何避免使用」的帖子。
  • 教你如何使用Python的27萬代碼庫
    Python代碼那麼多,怎麼拿來用你知道嗎?在Python有個巨大的寶庫PyPI,裡面有27萬開源的模塊供大家使用。很多讓你苦思冥想的功能,其他大神早就寫出來了。甚至不要copy粘貼,只要import下就能用了。PyPI是什麼?
  • 代碼詳解:Python虛擬環境的原理及使用
    · 確保執行Python代碼的腳本使用在給定虛擬環境中安裝的Python解釋器和站點包。最後一點在於會發生一些意想不到的錯誤,稍後會講這一點,但現在先看看在實際中如何實際使用虛擬環境。但丁《神曲·地獄篇》第六章—維吉爾安撫Cerberus插圖:Gustave Doré3.
  • 手把手:AlphaGo有啥了不起,我也能教你做一個(附Python代碼)
    在思考未來可能的情景時,優先考慮有前途的路徑,同時考慮其他人最有可能如何對你的行動作出反應,並繼續探索未知。遇到不熟悉的狀況,評估它的利害程度,把它同之前的各種讓你走到今天這一步的場景作比較。窮盡你對未來的想像,用你試過最多的招數來應對。在你考慮了未來的可能性之後,採取你已經探索過的行動。
  • 10行Python代碼也能實現,親測好用!
    大數據文摘出品編譯:朱一輝、雪清、小魚短短10行代碼就可以實現目標檢測?!本文作者和他的團隊構建了一個名為ImageAI 的Python庫,集成了現今流行的深度學習框架和計算機視覺庫。本文將手把手教你構建自己的第一個目標檢測應用,而且文摘菌已經幫你踩過坑了,親測有效!
  • 30段極簡Python代碼:這些小技巧你都Get了麼
    本文是 30 個極簡任務,初學者可以嘗試著自己實現;本文同樣也是 30 段代碼,Python 開發者也可以看看是不是有沒想到的用法。Python 是機器學習最廣泛採用的程式語言,它最重要的優勢在於編程的易用性。如果讀者對基本的 Python 語法已經有一些了解,那麼這篇文章可能會給你一些啟發。
  • Python教程|用代碼打開聖誕節的奇妙姿勢!
    如何使用Python來繪製一張聖誕小卡片呢?一起來動手做一張吧!首先,我們新建一個python文件。導入turtle庫,進行一些簡單的準備工作。如果你還不知道這些庫該怎麼使用,文末有關於turtle庫的過往教學哦!turtle.setup(1200,800)這行代碼的作用是將turtle庫提供的窗口大小設置為1200 * 800,你也可以設置成其他的大小。
  • Python學習第129課——醉漢隨機遊走代碼改進
    【每天幾分鐘,從零入門python編程的世界!】上節我們在Python中用代碼實現了醉漢隨機遊走的邏輯和過程,這節我們把上節的代碼改進一下。現在我們的小例子代碼是非常少的,實際開發中,有些項目代碼量會非常大,為了代碼在執行時有更快的速度,那麼就需要對代碼進行改進優化。
  • 如何使用python語言代碼實現判斷是否為回文
    工具Visual Studio 2019python運行環境技術python回文回文,是按照中心對稱,從左到右或從右到左,字符串都一樣的。如果想要python語言代碼實現回文判斷,若為回文,列印回文,否則列印不是回文。
  • Python編程題:兩個日期間的天數統計(附代碼)
    由於python中time模塊的很多函數都是可以直接計算出指定時間的時間戳(秒數),所以統計兩個日期間的總天數就非常方便了!代碼與運行結果:代碼與運行結果代碼解析:time1 = (int(t1[0]),int(t1[1]),int(t1[2]),0,0,0,0,0,0)這裡補足6個0是因為在struct_time類型中至少需要9個值,而已經有了年月日
  • 手把手教你用Keras進行多標籤分類(附代碼)
    本文將通過拆解SmallVGGNet的架構及代碼實例來講解如何運用Keras進行多標籤分類。本文的靈感來源於我收到的一封來自PyImageSearch的讀者Switaj的郵件。 我不想在if / else代碼的級聯中單獨應用它們,這些代碼使用不同的網絡,具體取決於先前分類的輸出。 謝謝你的幫助Switaj提出了一個美妙的問題:Keras深度神經網絡是否有可能返回多個預測?如果可以,它是如何完成的?
  • 一篇文章教你用11行Python代碼實現神經網絡
    聲明:本文是根據英文教程 (用 11 行 Python 代碼實現的神經網絡)學習總結而來,關於更詳細的神經網絡的介紹可以參考我的另一篇博客:。A Neural Network in 11 lines of Python從感知機到人工神經網絡如果你讀懂了下面的文章,你會對神經網絡有更深刻的認識,有任何問題,請多指教。
  • Python破解反爬蟲:最新反爬蟲有道翻譯中英文互譯破解,附代碼
    python這裡小編今天就給大家發一個最新的破解有道翻譯反爬蟲機制的python代碼,你也可以百度,但百度上目前的所有有道翻譯的爬蟲代碼都已經不能用話不多說,我們先看結果,代碼在第三幅圖爬蟲運行結果由於頭條屏蔽了空格,導致所有代碼縮進無法正常顯示
  • 教你用PyTorch實現「看圖說話」(附代碼、學習資源)
    注意: 本文假定你了解深度學習的基礎知識,以前曾使用CNN處理過圖像問題。例子的代碼可以在GitHub上找到。代碼的原始作者是YunjeyChoi 向他傑出的pytorch例子致敬。在本例中,一個預先訓練好的ResNet-152被用作編碼器,而解碼器是一個LSTM網絡。
  • Python趣味打怪:60秒學會一個例子,147段代碼助你從入門到大師
    不要害怕學習的過程枯燥無味,這裡有程式設計師jackzhenguo打造的一份中文Python「糖果包」:147個代碼小樣,60秒一口,營養又好玩,從Python基礎到機器學習盡皆囊括。你問啥是裝飾器?就像Python學習路上的一盒巧克力,60秒一口,讓你在一段段代碼的實踐中體驗編程的樂趣,步步」打怪「進階。
  • PyTorch最佳實踐,教你寫出一手風格優美的代碼
    請參閱 Google 提供的優秀的 python 編碼風格指南: 地址:https://github.com/google/styleguide/blob/gh-pages/pyguide.md。