手把手 | 嫌Python太慢?並行運算Process Pools三行代碼給你4倍提速!
大數據文摘作品,轉載要求見文末
作者 | Adam Geitgey
編譯 | 元元、Lisa、Saint、Aileen
Python絕對是處理數據或者把重複任務自動化的絕佳編程語言。要抓取網頁日誌?或者要調整一百萬張圖片?總有對應的Python庫讓你輕鬆完成任務。
然而,Python的運營速度一直飽受詬病。默認狀態下,Python程序使用單個CPU的單個進程。如果你的電腦是最近十年生產的,多數情況下會有4個及以上CPU核。也就是說,當你在等程序運行結束的時候,你的計算機有75%或者更多的計算資源都是空置的!
讓我們來看看如何通過並行運算充分利用計算資源。多虧有Python的concurrent.futures模塊,僅需3行代碼就可以讓一個普通程序並行運行。
一般情況下的Python運行
比如說我們有一個文件夾,裡面全是圖片文件,我們想給每一張圖片創建縮略圖。
下面的短程序中我們使用Python自帶的glob 函數獲取一個包含文件夾中所有圖片文件的列表,並用Pillow圖片處理庫獲取每張圖片的128像素縮略圖。
這個程序遵循很常見的數據處理模式:
1. 從您想處理的一系列文件(或其他數據)開始
2. 編寫一個處理一個數據的輔助函數
3. 用for循環調動輔助函數,一個一個的去處理數據
讓我們用1000張圖片來測試這個程序,看看運行時間是多少。
程序運行時間8.9秒,但是計算機的運算資源佔用了多少呢?
讓我們再跑一次程序,同時查看活動監視器:
計算機有75%空置,這是為什麼呢?
問題在於我的計算機有4個CPU核,但是Python只用了其中一個核。即便我的程序把那個CPU核完全佔滿,但是其他3個CPU核什麼也沒幹。我們需要想辦法把整個程序的工作量分成4份然後平行運行。所幸Python可以做到這一點!
讓我們來試試並行運算
下面是實現並行運算的一個方法:
1.把Jpeg圖片文件列表分成4個部分。
2. 同時跑四個Python解釋器。
3. 讓四個解釋器分別處理一部分圖片文件。
4. 匯總四個解釋器的結果得到最終結果。
四個Python程序分別在4個CPU上運行,跟之前在1個CPU運行相比大概可以達到4倍的速度,對不對?
好消息是Python可以幫我們解決並行運算麻煩的部分。我們僅需要告訴 Python我們想要運行什麼函數以及我們希望工作分成多少份,其他部分留給Python。我們只需要修改三行代碼。
首先,我們需要導入concurrent.futures庫。這個庫是Python自帶的:
然後,我們需要告訴 Python另外啟動4個Python實例。我們通過創建Process Pool來傳達指令:
默認設置下,上面的代碼會給計算機的每一個CPU創建一個Python進程,所以如果您的計算機有4個CPU,就會開啟4個Python進程。
最後一步是讓Process Pool 用這4個進程在數據列表中執行我們的輔助函數。我們可以把我們之前的for循環替代為:
新代碼是調用executor.map()函數
executor.map() 函數調用時需要輸入輔助函數和待處理的數據列表。這個函數幫我們完成所有麻煩的工作,把列表分成幾個小列表,把小列表分配給每個子進程,運行子進程,以及匯總結果。幹得漂亮!
我們也可以得到每次調用輔助函數的結果。executor.map()函數以輸入數據順序返回結果。 Python的zip()函數可以一步獲取原始文件名以及相應結果。
下面是經過三步改動之後的程序:
讓我們試著運行一下,看看有沒有縮短運行時間:
2.274秒程序就運行完了!這便是原來版本的4倍加速。運行時間縮短的原因正是我們這次用4個CPU代替了1個CPU。
但是如果您仔細看看,您會看到「用戶(User)」時間大概是接近9秒,如果程序2秒就運行結束了,為什麼客戶時間會是9秒?這似乎…有哪裡不對?
其實這是因為」用戶」時間是所有CPU時間的總和。我們和上次一樣,用9秒的總CPU
注意:啟用Python進程以及給子進程分配數據都會佔用時間,因此您不一定能靠這個方法大幅提高速度。如果您處理的數據量很大,這裡有一篇「設置chunksize參數的技巧」文章可能可以幫助您:https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Executor.map。
這種方法總能幫我的程序提速嗎?
當你有一列數據,並且每個數據都可以獨立處理的時候,使用Process Pools是一個好方法。這有一些適合使用並行處理的例子:
- 從一系列單獨的網頁伺服器日誌里抓取數據。
- 從一堆XML,CSV和JSON文件中解析數據。
- 對大量圖片數據做預處理,建立機器學習數據集。
但Process Pools不是萬能的。使用Process Pool需要在獨立的Python處理過程中將數據來回傳遞。如果你正在使用的數據不能在處理過程中有效的被傳遞,這種方法就行不通。你處理的數據必須是Python知道怎麼搞定的類型(https://docs.python.org/3/library/pickle.html#what-can-be-pickled-and-unpickled)。
同時,數據不會按照一個預想的順序被處理。如果你需要前一步的處理結果來進行下一步驟,這種方法也行不通。
那GIL怎麼辦?
你可能聽說過Python有一個全局解釋器鎖(Global Interpreter Lock,),縮寫為GIL。這意味著即使你的程序是多層的,每一層也只有一個Python命令能被執行。GIL確保任何時候都只有一個Python線程執行。 GIL最大的問題就是Python的多線程程序並不能利用多核CPU的優勢。
但Process Pools能解決這個問題!因為我們在運行單獨的Python實例,每個實例都有自己的GIL。這樣你就有了真正的並行處理的Python代碼!
不要害怕並行處理!
有了concurrent.futures庫,Python可以讓你簡簡單單地修改腳本,卻能立刻調用你電腦上所有CPU內核開足馬力地運行。不要害怕嘗試。一旦你會用了,它就像寫一個for循環那樣簡單,但會讓整個程序快很多。
推薦閱讀:
※數據可視化(六)常見的可視化儀錶盤(DashBoard)
※為什麼學校里學習雲計算或者大數據都要從hadoop開始?
※大數據技術中,關於用戶行為分析方面的有哪些技術?
※14/50—將近一個月的R語言學習心路歷程
※大數據時代,我們的隱私怎麼辦?
TAG:大数据 |