1. 程式人生 > >不得不服!Python速度雖然慢,但是它工作效率很高!

不得不服!Python速度雖然慢,但是它工作效率很高!

寫在前面

讓我們來討論一個我最近一直在思考的問題:Python 的效能。順便說一下,我是 Python 的忠實擁躉,我在各種情況下都會積極嘗試使用 Python 來解決問題。大家對 Python 最大的抱怨就是它的速度慢。有些人甚至因為 Python 的速度不如某個語言而拒絕使用它。本文中我將闡述,即便 Python 這麼慢,為什麼還值得你對它進行嘗試。

更多Python視訊、原始碼、資料加群960410445免費獲取

速度不再關鍵

之前,程式的執行時間相當長。CPU 資源和記憶體資源都十分珍貴,程式的執行時間在這種情況下是一個重要指標。計算機本身十分昂貴,當然還有隨之而來昂貴的電力消耗。優化這些資源就十分必要,因為在商業世界有一個永恆的規則:

優化你最昂貴的資源。

歷史上,程式最昂貴的資源是計算機的執行時間。這也就導致了對電腦科學的研究更專注於不同演算法的效率。然而在當下環境中,這已經不再適用,現在矽的價格已經十分便宜了。是真的非常便宜。執行時間不再是你最昂貴的資源。一個公司最昂貴的資源現在是其僱傭的員工的時間。也就是正在看這篇文章的你自己的時間。對現在的公司來說,完成專案比讓專案跑得更快更重要。這點非常重要,這裡再次強調:

完成專案比讓專案跑得更快更重要。

你也許會說“我們公司對效能要求很高,我構建的網站應用需要所有的請求在 X 毫秒內返回。”或者“客戶認為我們的應用慢而放棄使用我們的應用。”在這裡我不是說速度根本不重要,我只是想說明速度不再是最重要的指標,因為它不再是你最昂貴的資源。

不得不服!Python速度雖然慢,但是它工作效率很高!

速度!

速度是唯一重要的事情

在程式設計的世界中當你提到速度,一般是指程式的效能,也就是 CPU 週期。而當你的 CEO 提到速度,他通常指的是業務上的速度,其中最重要的是投入市場的時間。你的產品或網路應用有多快並不重要,應用採用哪種語言編寫的也不重要,甚至是使專案執行投入了多少資金都不重要。最終,唯一能夠讓你的公司存活下來的是產品投入市場的時間。

這裡不是指初創公司觀念中的盈利時間,而更多是從想法轉換到實際消費者手中所花費的時間。在商業世界中能存活下來的唯一方法是比你的競爭對手更快地進行創新。如果你的競爭對手比你更早地釋出產品,那麼你有再多的好點子也無濟於事。你必須成為市場的第一個進入者,或至少要趕上領先的節奏。一旦你掉隊了,那麼你就大勢已去。

在商業世界中能存活下來的唯一方法是比你競爭對手更快地進行創新。

微服務的例子

亞馬遜、谷歌、Netflix 等公司深刻理解速度的重要性。它們建立了一個能快速發展和創新的業務系統。微服務就是這個問題的解決方案。本文並不討論你是不是應該使用微服務,但最起碼亞馬遜和谷歌認為它們應採用微服務。

不得不服!Python速度雖然慢,但是它工作效率很高!

 

微服務天生就很慢。微服務的最基礎的概念就是拆分業務邊界,並通過網路呼叫來相互通訊。這也就意味著你需要把一個只佔幾個 cpu 週期的方法呼叫轉換成網路呼叫。從效能層面上來說,這簡直糟糕透頂。網路呼叫的速度和 CPU 呼叫根本不可同日而語。但是那些大公司仍然選擇使用微服務。沒有比微服務更慢的架構了。

微服務的最大劣勢就是其效能,但是它所帶來的最大好處是縮短了投入市場需要的時間。通過構建小型專案和少量程式碼的團隊,公司可以以一個非常快的速度進行迭代與演進。這個例子只是為了展示不僅僅是初創公司,大公司也關注投入市場所需的時間。

CPU 並非你的瓶頸

 

不得不服!Python速度雖然慢,但是它工作效率很高!

 

如果你編寫像網路伺服器上的網路應用,那麼 CPU 時間可能並非你應用的瓶頸。當你的網路伺服器處理一個請求,它可能會需要呼叫多個網路呼叫,例如資料庫或 Redis 快取。這些服務本身速度很快,然而網路呼叫的過程卻很慢。一篇部落格很好地描述了各個特定操作速度上的差別。其中,作者將 CPU 時間對應到人們易於理解的時間。如果單個 CPU 週期對應一秒的話,一個從加利福尼亞到紐約的網路呼叫就大約相當於 4 年。

對,網路呼叫就是這麼慢。粗略地估計,在同一資料中心內的一個普通的網路呼叫需要 3 毫秒,這在前面的對應關係下相當於 3 個月。現在假如你的程式是 CPU 密集型的,需要花費 100,000 個 CPU 週期來處理一次呼叫。按之前的比例來算,這些時間相當於 1 天。那麼如果你用一個慢 5 倍的語言,它也就只花費了 5 天。相對於 3 個月的網路呼叫,4 天的差別就無足輕重了。如果使用者在等待一個至少需要 3 個月的包裹,那麼 4 天的差別相對來說就不那麼重要了。

說了這麼多我只是想說,即便 python 很慢,但這並不重要。語言的速度(也就是 CPU 時間)幾乎不會導致問題。谷歌就這個概念做過一個研究,並寫了一篇論文。論文中談論了設計高吞吐量的系統。在結論中這樣描述到:

在一個高吞吐量的環境中使用一個解釋型語言看似矛盾,但是我們發現 CPU 時間幾乎不是瓶頸因素,表達性強的語言意味著大部分程式碼是短小的,大多數時間花費在了 I/O 以及原生程式碼執行時上。此外,解釋型的實現所具備的靈活性十分有用,它方便了我們在語言層面上的試驗,也方便了我們探索將計算分佈到多臺機器上的方法。

簡單說來:

CPU 時間幾乎不是瓶頸因素。

那如果 CPU 時間的確是系統的瓶頸呢?

你可能會說“這觀點很好,但是我們確實在 CPU 上遇到了瓶頸,造成了我們網路應用的速度緩慢”,或者“在伺服器上 X 語言相對 Y 語言需要更少的硬體資源來執行。”這可能都是事實。但網路應用的優勢就是你可以幾乎無限地進行負載均衡。換而言之,就是使用更多的硬體資源。當然 Python 相較其他語言,如 C 語言,可能需要更多硬體資源。那就使用更多的硬體來解決這個問題。硬體相對於你的人工時間便宜許多。如果你一年內節約了幾周的開發時間,這就遠勝於你在硬體上所節約下來的花費。

那麼,Python 到底快不快?

前面我談論了最重要的是開發所花費的時間。但是問題還是沒有得到回答:Python 的開發時間的確比其他語言快麼?經過多方調查,我、谷歌以及許多第三方結論都會告訴你 Python 能提升多大產能。Python 抽象化了諸多內容,可以讓你專注於你真正的業務邏輯,而不用關心你是應該使用 vector 還是 array 等底層細節問題。你可能不相信這道聽途說的觀點,所以讓我們看一些經驗資料。

總體來說,爭論 python 是否高產,最終討論的是指令碼(或動態語言)與靜態型別語言之間的比較。我認為大家都贊同靜態型別語言的產量較低,但這裡有一篇很好的論文解釋了其中的原因。就 Python 而言,曾有研究分析了不同語言編寫一個字串處理程式所花費的時間,並做了很好的總結。

不得不服!Python速度雖然慢,但是它工作效率很高!

 

使用不同語言編寫字串處理應用所花費的時間。(Prechelt 與 Garret)

在結論中 Python 比 Java 的生產效率高兩倍。還有其他諸多研究結果得到類似的結論。Rosetta Code 對不同語言進行了公平而深入地研究。在論文中它們將 Python 和其他指令碼 / 解釋型語言進行了比較,並認為:

Python 是其中最精練的,甚至比函式式語言更好(平均短 1.2-1.6 倍)。

總體看來 Python 程式碼的行數總是更少。程式碼行數聽上去是一個糟糕的指標,但是多項研究顯示(包括之前提及的兩個),在各語言中輸入每行程式碼的時間是不相上下的。因此,減少程式碼行數也就相當於提高了生產效率。就連 C# 程式設計師 codinghorror 也寫了一篇文章闡述 Python 具有更高的產量。

我認為這已經足夠能說明 Python 相較於諸多其他語言更高產。這主要歸功於 Python 的開箱即用以及豐富的第三方包。這裡有一篇文章簡述了 Python 和其他語言的差別。如果你不知道為什麼 Python 這麼“小”還這麼高產,我推薦你學習一下 Python 來親自體驗一下,下面將是你的第一行程式:

import __hello__

如果執行速度對你真的很重要?

 

不得不服!Python速度雖然慢,但是它工作效率很高!

 

上述觀點的論調聽上去像認為優化和速度根本不重要。但是事實是,許多時候執行時效率至關重要。一個例子是,你的網路應用有一個特定的端點需要相當長的時間來響應請求。同時你知道它需要有多快,也知道它要被優化到什麼程度。

在這個例子中,發生了下面兩件事:

  1. 我們關注到某個執行慢的端點。
  2. 我們認為它慢,因為我們瞭解什麼是足夠快,並且它沒能達到這個指標。

我們不必在應用中對每個服務進行細節調優。每個服務只需要能“足夠快”來滿足使用者的需求就夠了。使用者會發現某個端點花費了幾秒時間返回,但是他們並不會注意到你把一個 35 毫秒的請求優化到了 25 毫秒。你只需要達到“足夠好”就可以了。免責宣告:不得不說一些應用,如實時拍賣應用,確實需要細節調優,能提升一毫秒算一毫秒。但是這是一個特例,而不是業界的規則。

為了弄清如何優化某個端點,第一步你需要對你的程式碼進行效能分析,並嘗試整理出其中的瓶頸。歸根到底:

任何不考慮瓶頸的調優都是幻想。—— Gene Kim

如果你的優化並不解決瓶頸,那你就是在浪費你的時間,而且還不能解決真正地問題。不解決瓶頸,你就不會在效能上得到顯著的提升。如果你嘗試著在瞭解瓶頸前優化,你就像在和你的程式碼在玩打地鼠的遊戲。在排查和確定瓶頸前優化程式碼也是“不成熟優化”的表現。Donald Knuth 常被引用下面的觀點,雖然他本人稱這也是從其他人那兒聽來的:

不成熟的優化是萬惡之源。

Donald Knuth 在一次關於維護程式碼庫的討論中進行了下面的完整描述:

我們應該忘記那些小的效能提升,這佔了 97% 的時間:不成熟的優化是萬惡之源。但同時我們也不能放過那至關重要的 3%。

換句話來說,大部分時間,你不應該關心程式碼的優化。它們通常已經足夠好了。如果沒有能達到標準,我們應該只需要改變那 3% 的程式碼。你並不會因為你的程式碼使用一個 if 替代了一個方法,得到幾毫秒的效能提升而獲得任何獎勵。只有在分析之後再進行優化。

不成熟的優化包含盲目呼叫某個更快的方法,或使用一個特定的資料結構只因為其總體上更快。電腦科學認為兩個方法或演算法有一樣的漸進增長(或時間複雜度),那麼就可以認為它們效能是相同的,就算其中之一比另一個慢兩倍。計算機的速度太快,演算法在計算上的增長,如資料或使用量的增長比演算法本身重要得多。

換而言之,如果你有兩個 O(log n) 的方法,一個是另一個速度的兩倍,這之間的差別根本無關緊要。隨著資料量的增長,它們都會以相同的速度變慢。這也就是為什麼不成熟的優化是萬惡之源,它會浪費我們的時間,最終卻在提升效能上幫不上我們什麼忙。

就時間複雜度而言,你可以認為用任何的語言寫你的程式的複雜度都是 O(n) 的,其中 n 是程式碼的行數或指令個數。同一指令的增長速率都是相同的。所以一個語言或執行時的快慢並不重要,就漸進增長而言,所有語言都是等價的。在這個邏輯下,你可以認為,因為某個語言速度快而選擇其為開發你應用的語言是不成熟優化的一種體現。你不應該主觀地判斷某個語言快而不去進行衡量、不去了解將會遇到的瓶頸。

因為某個語言速度快而選擇其為開發你應用的語言是不成熟優化的一種體現。

 

不得不服!Python速度雖然慢,但是它工作效率很高!

 

 

優化 Python

我最喜歡 Python 的一點就是它可以讓你一步一步地優化你的程式碼。比如說你有一個 Python 方法,你發現它是你專案中的瓶頸。你已經對其優化了數次,可能是遵循了這裡或這裡的意見,現在你確定 Python 本身是你應用的瓶頸所在。

Python 是能夠直接呼叫 C 程式碼的,這就意味著你可以用 C 重寫這個方法來減少效能問題。你可以一個一個地進行替換。這個過程能讓你呼叫任何最終編譯成 C 相容指令的優化的程式碼,也讓你能在大部分情況下繼續使用 Python,而只在真正需要的時候深入底層進行開發。

有一個叫 Cython 的語言,它是 Python 的超集。幾乎是 Python 和 C 的結合體,同時它是漸進的型別化語言。任何 Python 程式碼都是合法的 Cython 程式碼,Cython 會將程式碼編譯成 C 程式碼。有了 Cython,你可以編寫模組或方法,漸漸地引入 C 語言的型別和效能。你可以混合使用 C 語言的型別和 Python 的鴨子型別(duck type)。通過 Cython 你可以只在瓶頸處進行調優,而在其他地方仍然使用優美的 Python 語言,兩者能完美地結合。

不得不服!Python速度雖然慢,但是它工作效率很高!

使用 Python 編寫的太空大規模多人線上遊戲 EVE Online 的截圖

當你最終遇到了 Python 的效能瓶頸,你不需要將你所有程式碼移植到其他語言。你總是可以使用 Cython 重寫部分方法來滿足效能上的需求。這也是遊戲 EVE Online 所採用的策略。Eve 是一個大型多人線上電腦遊戲,它完全使用 Python 和 Cython 開發。遊戲開發人員通過在 C/Cython 中調優瓶頸來達到遊戲級的效能要求。如果遊戲都能達到效能上的需求,那麼大部分情況都應該可以滿足。

此外,還有其他方法來優化你的 Python 程式。例如 PyPy 是一個 Python 的執行時編譯執行(JIT)的實現,只需要使用 PyPy 切換預設的 Cython,就可以顯著地提升你長時間執行應用的執行時效能,如在網路伺服器上。