閱讀757 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Python確實比較慢,但我不在乎

0?wx_fmt=jpeg

生產力的增長是靠犧牲性能換來的。這篇文章不再討論asyncio(異步IO庫)在Python中的運用,而是談談最近我一直在思考的一個問題:Python的運行速度。同那些不了解Python的人相比,我屬於Python的忠實粉絲,而且我使用Python的頻率非常高。目前人們抱怨Python最多的是它的運行速度慢。通常,大部分人拒絕使用Python是因為它比某某語言還慢。盡管如此,我還是建議你使用Python,理由如下:


速度不再重要


以前,運行程序需要花費很長的時間。CPU和內存的售價是比較昂貴的,衡量這兩個硬件的一個重要標準是程序運行所花費的時間。電腦和運行所需的電力的價格也非常昂貴。要想優化這些資源,需要基於一條永恒的商業法則:


優化最貴的資源

從曆史上來看,最昂貴的資源無疑是計算機運行時間。這就導致了計算機科學注重研究不同算法的效率。然而,隨著矽變得便宜,一切都不一樣了。運行時間不再是最昂貴的資源。企業目前最為昂貴的資源是雇員的時間。或者換種方式來講,最重要的是讓雇員在規定時間內將程序完成,而不是讓程序運行速度更快。事實上,這是很重要的,我再次將它放在這裏來引出下麵的內容(對於那些僅僅想瀏覽一下文章的人來說):

程序的開發時間比運行速度更重要。

你可能會說,“我的公司隻關心速度,我開發的web程序的所有響應速度都比其他語言快幾毫秒”。或者,“我們的客戶取消訂單是因為覺得我們的程序太慢了”。我並不是想說速度不重要,而是想說,它不再是最重要的問題,同時也不再是最昂貴的資源。


速度!是唯一重要的事情

0?wx_fmt=jpeg

你認為的編程環境中的速度,其實指的是性能,即CPU周期(機器周期)。而你的BOSS說的編程環境中的速度指的是公司運營的速度,其最重要的標準就是上市時間。最終,無論你的產品還是web應用運行多快,無論它是用什麼語言編寫的,無論它的運行費用是多少,在一天結束的時候,決定你公司生存與否的是何時上市。我談的不止是一個創業想法最終盈利需要多長時間,而是整個時間架構——“從想法到最終交付”。企業生存的唯一方法就是比你的競爭對手的創新速度更快。無論你想出多少好的主意,一旦你的競爭對手比你更先“落實”,一切都付之東流。你必須第一個進入市場,至少也要保持領先。一旦你的創新速度變慢了,那麼你就可能要完蛋了。

唯一能在商業中存活的方法便是創新速度要快過你的競爭者。


案例:(微服務架構)

像亞馬遜、穀歌和Netflix(美國付費視頻網站)這樣的公司十分清楚快速發展的重要性。他們已經創建了一個業務係統以便更快地發展和創新。微服務架構解決了他們的困難。本文並不打算推薦你使用微服務架構,但是至少幫助你理解為什麼亞馬遜和穀歌應該使用這一架構。

0?wx_fmt=jpeg


微服務架構運行一直都比較緩慢,它打破了網絡調用的限製。這意味著你將函數調用(一組CPU周期)變成一個網絡調用。相信沒有什麼比這個更糟糕的體驗了。同CPU相比,網絡調用超級慢。盡管我相信沒有哪個架構程序比它更慢了,但這些大公司仍然選擇使用微服務架構。微服務架構最大的缺點是性能,最大的優點就是上市時間。通過圍繞小項目和代碼庫建立團隊,公司能夠更快的迭代與創新。這表明了不僅僅是創業公司,大公司也同樣關心產品的上市時間。


CPU不是你的瓶頸

0?wx_fmt=jpeg


如果你編寫一個應用程序,比如web服務器,有可能CPU時間並不是你遇到的最大的困難。當你的web服務器處理一個請求時,它可能生成一組網絡調用,比如會用到數據庫,或像Redis這樣的緩存服務器。盡管這些服務本身響應迅速,對它們網絡調用卻比較緩慢。有篇不錯的博客就指出了某些操作的速度差異。在這篇文章中,作者用人類時間來解釋CPU cycle times(機器周期),如果一個單獨的CPU周期為一秒的話,從加利福利亞到紐約的一個網絡調用則需要花費4年的時間,這太慢了!據粗略估計,假設在同一數據中心內部的普通網絡調用需要3毫秒,這相當於我們“人類時間”的3個月。現在,假設你的程序屬於CPU密集型,需要10000個周期來響應一個調用,這差不多超過一天了。如果你使用的語言要慢5倍,現在大約就需要5天了。當然,同3個月的網絡調用相比,多了4天沒多大區別。如果有人為一個數據包需要等待至少3個月以上,那麼我不認為多等四天對他們來說有多重要。

最終的結論是:即使python很慢,也影響不大。語言的速度(或CPU時間)幾乎從來不是問題。穀歌實際上對這個概念做過一項研究,並寫成了一篇研究報告,討論了高吞吐量係統的設計。

總結來說,在高吞吐量環境中使用解釋型語言似乎是荒謬的。但是我們發現,CPU時間基本上不屬於限製因素。語言的表達性指大部分程序都比較小,同時將大部分時間都耗費在I/O讀寫和代碼運行上了。此外,靈活的解釋型語言無論是在語言層麵的實現上,還是在探索很多機器的分布計算方法上,都有所幫助。總之——

CPU時間幾乎不是限製因素!


如果CPU時間是個問題呢?

你可能會說,“這樣的結論太好了。但是我們的確遇到了CPU的限製,並因此導致我們的web應用程序運行十分緩慢”或“在服務器上使用某某語言會比其他語言需要更少的硬件支持”。上述情況的確可能會發生。但web服務器的好處是,你可以無限地負載均衡。換句話說,可以裝更多的硬件。當然,Python可能比C語言等其他語言需要的硬件要多。用硬件來解決CPU的問題吧,硬件比你的時間可便宜多了。如果你在一年的工作時間中能節約出幾周時間,那麼其價值將超過額外的硬件成本。


所以,Python更快嗎?

0?wx_fmt=jpeg

在這篇文章裏,我一直都在討論開發時間是最重要的。所以還有一個問題:在開發時Python會比其他語言更快嗎?有趣的是,我、穀歌和其他一些人都可以證明python的效率更高。它可以為你簡化許多事情,幫助你專注於真正想實現的代碼,而不是陷入使用向量還是數組這類的瑣事。但是你可能不會相信我所說的,所以讓我們來看一組數據:

大多數時候,關於python是否更有效率的爭論,可以歸結為腳本程序(或動態語言)和靜態語言之爭。我認為大家普遍接受的是靜態語言的效率更低。但是有一篇很不錯的論文解釋了為什麼不是這樣。特別對Python而言,這裏有一篇研究很好地總結了各種語言編寫字符串處理程序所需的時間。

0?wx_fmt=png

不同的編程語言在編寫字符串處理程序時所需要的時間

上述研究顯示,相同的時間內Python的產出是Java兩倍多,還有很多其他的研究也得到了相同的結論,羅塞塔編程(Rosetta Code 一個用不同語言解決相同任務的維基網站)曾經做過關於不同編程語言差異性的深度研究,在研究中,他們對比了Python和其他的腳本/解釋性語言,得到的結論是“Python往往是最簡潔的語言,甚至匹敵函數式編程語言(平均編寫時間比其它語言節省1.2到1.6倍)”

大體上看來,Python中代碼的行數通常很短。雖然行數看起來是一個糟糕的度量標準,但是很多的研究,包括之前提到的研究表示,在所有的編程語言中,每行代碼的運行時間基本相同。因此,少的代碼行數提高了程序的產出率。甚至連coding-horror(Jeff Atwood,一個C#程序員)也寫過一篇文章論述Python代碼的產出是多麼的高。

在我看來,說Python要比其他所有語言都要多產是恰當的,這主要是因為Python有很多內建模塊並且有許多第三方庫。Python官網上有一篇文章探討Python和其他的區別(https://www.python.org/doc/essays/comparisons/)如果你不知道為什麼Python如此的輕量並且高效,我建議你借此機會去學習一些Python然後自己去探索。這就是你的第一個程序:

import __hello__


如果速度真的很重要怎麼辦?


0?wx_fmt=jpeg


聽起來,上述觀點的論調可能讓人覺得優化和運行速度一點也不重要,但是事實是,在很多時候,程序的性能確實非常重要。舉一個例子,你有一個web應用程序,有一個終端需要花費很長的時間響應。你知道它需要多快,需要改進多少。

在這個例子中,發生了如下事情:

  1. 我們發現了一個終端響應很慢。

  2. 我們發現它慢,是因為我們有一個標準去衡量什麼才算足夠快,但是這個終端沒有達到標準。

我們沒有必要對應用程序中的所有內容都做微小的優化,隻需要讓每一部分足夠快就可以了。你的用戶可能會發現一個終端花費了幾秒才應答,但是他們不會發現你把應答時間從35毫秒縮短到了25毫秒。“足夠好”就已經是你所需要達到的全部了。免責聲明:我應該指出,確實存在一些應用程序,例如實時招投標程序,需要微小的優化,而且每一毫秒都很重要。但是那是個例外,並不能成為準則。

為了明白如何優化終端,第一步你要做的就是縱覽整個程序試著找出影響速度的瓶頸在哪裏,畢竟,Gene Kim說過,

“不在速度瓶頸處的程序優化都是浮雲。”

如果你的優化沒處在整個程序最需要的地方,你就是在浪費時間。除非你在瓶頸處做了優化,否則你的程序不會有實質性的速度提升。如果你在找到瓶頸之前就開始著手優化,這就像是在和你的代碼玩打地鼠一樣毫無作用,這種在你查找並確認瓶頸前進行的優化被叫做過早優化。有一句話批評了這種行為,很多人認為這句話是Donald Knuth說的,但是他自己說是照搬別人的,那就是:“過早優化是萬惡之源”

談到維護代碼庫,Donald Knuth的原話是:

“在97%的情況下,我們應該忽略小的效率提升,即過早的優化是萬惡之源,然而我們不應該錯過那關鍵的3%。”

換句話說,他在說在大部分的情況下,你不應該總想著優化你的代碼,它幾乎足夠好了。在你的代碼不夠好的時候,我們通常隻需要變動3%的代碼。讓你的終端響應快幾納秒,你不會獲得任何的好處,因為你可能隻是用if語句代替一個函數。所以在你想清楚哪裏是瓶頸後,再去優化代碼。

過早的優化包括調用一個更快的方法,甚至使用一個更快的數據結構。計算機科學顯示如果兩種方法或算法有著相同的漸進增長速度(或者叫大O)那麼他們就是相同的,即使在實際使用中速度相差兩倍。計算機的計算速度太快了,以至於由數據量增長導致的計算量增大要比它實際的計算速度更重要。換句話說,如果我們有兩個大O(log n)即log n級的函數,但是一個比另一個慢兩倍,這沒什麼影響,因為隨著數據量的增加,它們會以相同的速率減慢。這就是為什麼“過早的優化是萬惡之源”,這種優化浪費了我們的時間, 也從未真正地提高運算速度。

從大O的角度考慮,你可以認為所有的編程語言都是大O(n)級的算法,其中n是代碼或者指令的行數。一種編程語言或者程序運行時間慢並不是問題,從漸進增長速度的角度考慮,所有的編程語言生而平等。從這個邏輯出發,你可以認為,憑借“速度”因素為應用程序選擇一個編程語言完全屬於過早的優化,你實際選擇的是“據說”很快的編程語言,而沒有去測試、沒有理解影響速度的瓶頸是什麼。

憑借“速度”因素為應用程序選擇一個編程語言完全屬於過早的優化。


優化Python

0?wx_fmt=png

我喜歡Python的一點是它可以讓你每次優化一點點你的代碼。假設你發現一個用Python實現的方法是限製了你代碼的速度,並且你可能參照Python速度 或Python性能指南 這樣的文檔,將代碼優化了很多次,你現在已經非常確信Python本身就是運行速度的瓶頸。但Python能夠調用C代碼,這也意味著你可以用C語言重寫這部分代碼從而提高性能,你可以一次改寫一個方法。這個過程允許你可以使用任何能以C語言的方式編譯的語言,寫出很多優化瓶頸的程序。這可以讓你在大部分時間裏都專注於Python,隻有在你真正需要的時候再使用較低級的語言。

有一種編程語言叫Cython,是Python的一個超集。它可以被粗略的認為是Python和C語言的融合,是一種漸進式的語言。任何的Python代碼都是有效的Cython代碼,而且Cython可以編譯成C代碼。有了Cython,你可以寫一個模塊或者方法,然後逐漸地形成更多C類型文件和可執行文件,你也可以融合C類型和Python的鴨子風格(動態類型的一種風格)。有了Cython,你可以隻在瓶頸處融合優化了的代碼,並且在別處保留Python語言的美。

0?wx_fmt=jpeg

星戰前夜的截圖:一個用 Python 編寫的 space MMO 遊戲。

當你終於遇到Python的性能問題時,你不需要把全部的代碼都改用其它的語言編寫。你隻需要在Cython中重寫幾個方法,就能得到你滿意的性能,這就是星戰前夜 (EVE Online)所用的策略。星戰前夜是一個宏大的多人電腦遊戲,使用Python和Cython作為整個架構,通過用C語言和Cython優化代碼的瓶頸,實現了遊戲級別的性能。如果這種優化方式適用於星戰前夜,那麼它也能滿足大多數人的需求。另外,也有其他的方法可以優化你的Python程序。例如,PyPy是Python的一種JIT(即時編譯)實現。對於長時間運行的程序(如web服務器),通過將Cython(Python的默認實現)改為PyPy,可以很好的縮短運行時間。

0?wx_fmt=jpeg


讓我們回顧一些要點:

  • 優化你最昂貴的資源,那就是你自己!不是計算機。

  • 選擇一個語言/框架/架構可以使你的快速開發(例如Python)。不要選擇那些僅僅是運行很快的技術。

  • 當你的確有性能問題時,找到你程序的瓶頸在哪裏。

  • 程序的瓶頸通常不是CPU或者Python語言本身。

  • 如果Python本身成了程序運行的瓶頸(你已經優化了你的代碼),那麼可以轉到熱門的Cython或者C語言上。

  • 盡情享受把事情迅速搞定的快樂吧。


原文發布時間為:2017-5-3

本文來自雲棲社區合作夥伴“大數據文摘”,了解相關信息可以關注“BigDataDigest”微信公眾號

最後更新:2017-05-17 00:45:36

  上一篇:go  程序員之痛點:取個好名字
  下一篇:go  實現“家門口”醫院不在夢,智慧醫療雲平台已啟動!