Python 速度要項

目前在寫資料 ETL 的系統程式,做了一個月,差不多能掌握的都夠了。

處理大量資料,最容易被要求、挑戰的就是執行速度。在 Python 來說,我感覺,速度不只端看程式的速度,而是搭配著看執行環境與系統。

在追求速度方面,寫程式的生存、自救要點,我覺得有以下幾項:

  1. 寫程式同時順便要做好速度量測的基礎,也就是資料率:速度是相對的,不同的電腦之間,同一份程式,慢的電腦跑得久,快的電腦跑得時間短。小時候學物理學,第一課就是在教我們識別各種單位表示與意義。同理,做大量資料處理,資料率是個很重要的東西。至少,我們要知道全部的資料有多少行,以及每一單位時間可以處理掉多少行。而所謂「需要多久可以跑完」這種模糊的標準,則是流水浮雲,請記得:時間是相對的。
  2. 不要把每一行都印出來:每一行電腦程式,依運作的特性,可以區別為 CPU-bound 與 IO-bound 等類型(我看可能這年頭,可以說有 NIO-bound 、 Cloud-bound 之類的,至少我看 ETL 的 Load 動作,要面對 MySQL-bound 的程式),好比 SATA 硬碟不會跟 IDE 硬碟接在同一條排線的概念,當你的一段程式夾雜了很多 IO 的程式行數,那一段程式就是 IO-bound ,速度會讓 IO 拖慢。而另一個不要每一行都印出來看的理由,是因為大量的資訊印出,我們的動態視力與注意力都跟不上,所以別白費工夫。
  3. 不同的 class 是不同的演算法:學習 Python 要多多運用 type(…) 或 module inspect ,來認識我們所能寫下的各種資料的本質。好好練過幾次,我們可以得到印象: str, list, dict, set 等等,各自包含了各種操作方法,代表了不同的演算法、有不同的速度。 str.find(…) 及 str.rfind(…) 分別用來搜尋二端的子字串,比 re.match(…) 快, list 方便連續串聯資料, dict 方便快速從中間添加或刪除資料,還有, dict 和 set 的查詢都比較快。迴圈方面, enumerate(…) 做資料列舉比 dict 快、 dict 做資料列舉比 list 快。
  4. 跟著資料的特性做資料處理:如果有一批資料的變動量極少,那麼,我們應該給這批資料用 set 或 dict 做個查詢表,給新來的一批資料找出差集合,那麼,所找出的差集合的資料量相當少,而且是所需處理的資料量。
  5. 跟著所要的流程裁切掉不必要的動作:通常寫程式的人都愛用模組化,或將同一程式格式 copy-paste 而產生另一份程式。組裝式、軟體工程式的程式,會帶來一些比較僵化的結構,也許就在結構中存有依些浪費時間的流程死角。程式寫得差不多要完成了,輪到了檢查程式執行速度的階段時,我們可以藉由反省口說的流程(不是程式的流程)、適當地打破格局,來重寫出比較快速的程式。在那之前,可以將程式拆解,借註解符號停用一些動作,來分析幾個零星段落的具體速度,最好能細緻發現到平均每一行消耗多少微秒。將每個動作單元每一行所消耗微秒數列出為列表,能幫助我們好好構想,並且可能從正規的軟體開發階段,跳躍到奇技淫巧的設計階段。或許你會發現,每一行都用好幾個 function application 做 extract ,會多消耗 20 ms ,那麼,那些漂亮的 lambda expression 就是需要清掉的東西。
  6. 別忘了傳統的節能、加速辦法:例如不要在迴圈的每一行都 try-except 一次,要在迴圈之外一次包覆 try-except 格式,會跑得比較快。
  7. 對於習慣不經意看待的東西,留一點時間多想好幾次:例如我有個程式段落寫成 dict([ x for x in dict(d) if x not in set(s) ] + [ (‘a’, ‘, ‘.join(‘hello’, ‘world’)) ]) ,這種寫法是一般 Prolog 或 Erlang 程式寫手所習慣的 generation way :我們手上已經有一個 dict d ,卻將它拆開然後重做成一個新的 dict d’ ,這是個很消耗時間的方法。若我們切換為減法思維,則是以從 dict d 裏頭刪除不需要的 key-value 著手, d.pop(k) 的速度相當快,用不同的方式達成相同的目標,而且獲得速度。

如以上,連番努力過好幾輪,相信我們的白髮都發得比較多。我認為白髮是一種榮譽。

廣告

About 黃耀賢 (Yau-Hsien Huang)

熱愛 Erlang ,並且有相關工作經驗。喜歡程式語言。喜歡邏輯。目前用 Python 工作。
本篇發表於 Uncategorized。將永久鏈結加入書籤。