Python Map 并行
Map是一個酷酷的小東西,也是在Python代碼輕松引入并行的關鍵。對此不熟悉的人會認為map是從函數式語言(如Lisp)借鑒來的東西。map是一個函數 - 將另一個函數映射到一個序列上。例如:
urls = ['http://www.yahoo.com', 'http://www.reddit.com'] results = map(urllib2.urlopen, urls)?
這段代碼在傳入序列的每個元素上應用方法urlopen,并將所有結果存入一個列表中。大致與下面這段代碼的邏輯相當:
results = [] for url in urls: results.append(urllib2.urlopen(url))?
Map會為我們處理在序列上的迭代,應用函數,最后將結果存入一個方便使用的列表。
這為什么重要呢?因為利用恰當的庫,map讓并行處理成為小事一樁!
Python標準庫中multiprocessing模塊,以及極少人知但同樣出色的子模塊multiprocessing.dummy,提供了map函數的并行版本。
題外話:這是啥?你從未聽說過這名為dummy的mulprocessing模塊的線程克隆版本?我也是最近才知道的。在multiprocessing文檔頁中僅有一句提到這個子模塊,而這句話基本可以歸結為“哦,是的,存在這樣一個東西”。完全低估了這個模塊的價值!
Dummy是multiprocessing模塊的精確克隆,唯一的區別是:multiprocessing基于進程工作,而dummy模塊使用線程(也就帶來了常見的Python限制)。因此,任何東西可套用到一個模塊,也就可以套用到另一個模塊。在兩個模塊之間來回切換也就相當容易,當你不太確定一些框架調用是IO密集型還是CPU密集型時,想做探索性質的編程,這一點會讓你覺得非常贊!
開始
為了訪問map函數的并行版本,首先需要導入包含它的模塊:
# 以下兩行引入其一即可 from multiprocessing import Pool from multiprocessing.dummy import Pool as ThreadPool?
并實例化池對象:
# 譯注:這里其實是以dummy模塊為例 pool = ThreadPool()?
這一句代碼處理了example2.py中7行的build_worker_pool函數完成的所有事情。如名所示,這句代碼會創建一組可用的工作者,啟動它們來準備工作,并將它們存入變量中,方便訪問。
pool對象可以有若干參數,但目前,只需關注第一個:進程/線程數量。這個參數用于設置池中的工作者數目。如果留空,默認為機器的CPU核數。
一般來說,如果為CPU密集型任務使用進程池(multiprocessing pool),更多的核等于更快的速度(但有一些注意事項)。然而,當使用線程池(threading)處理網絡密集型任務時,情況就很不一樣了,因此最好試驗一下池的最佳大小。
pool = ThreadPool(4) # 將池的大小設置為4?
如果運行了過多的線程,就會浪費時間在線程切換上,而不是做有用的事情,所以可以把玩把玩直到找到最適合任務的線程數量。
現在池對象創建好了,簡單的并行也是彈指之間的事情了,那來重寫example2.py吧。
import urllib2 from multiprocessing.dummy import Pool as ThreadPool urls = ['http://www.python.org', 'http://www.python.org/about/','http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html','http://www.python.org/doc/','http://www.python.org/download/','http://www.python.org/getit/','http://www.python.org/community/','https://wiki.python.org/moin/','http://planet.python.org/','https://wiki.python.org/moin/LocalUserGroups','http://www.python.org/psf/','http://docs.python.org/devguide/','http://www.python.org/community/awards/'# 等等... ]# 創建一個工作者線程池 pool = ThreadPool(4) # 在各個線程中打開url,并返回結果 results = pool.map(urllib2.urlopen, urls) #close the pool and wait for the work to finish # 關閉線程池,等待工作結束 pool.close() pool.join()?
看看!真正做事情的代碼僅有4行,其中3行只是簡單的輔助功能。map調用輕松搞定了之前示例40行代碼做的事情!覺得好玩,我對兩種方式進行了時間測量,并使用了不同的池大小。
# 譯注:我覺得與串行處理方式對比意義不大,應該和隊列的方式進行性能對比 results = [] for url in urls:result = urllib2.urlopen(url)results.append(result)# # ------- 對比 ------- # # # ------- 池的大小為4 ------- # pool = ThreadPool(4) results = pool.map(urllib2.urlopen, urls)# # ------- 池的大小為8 ------- # pool = ThreadPool(8) results = pool.map(urllib2.urlopen, urls)# # ------- 池的大小為13 ------- # pool = ThreadPool(13) results = pool.map(urllib2.urlopen, urls)?
結果:
單線程: 14.4 秒 池大小為4時:3.1 秒 池大小為8時:1.4 秒 池大小為13時:1.3秒真是呱呱叫啊!也說明了試驗不同的池大小是有必要的。在我的機器上,池的大小大于9后會導致性能退化(譯注:咦,結果不是顯示13比8的性能要好么?)。
現實中的Example 2
為千張圖片創建縮略圖。
來做點CPU密集型的事情!對于我,在工作中常見的任務是操作大量的圖片目錄。其中一種圖片轉換是創建縮略圖。這項工作適于并行處理。
基本的單進程設置
from multiprocessing import Pool from PIL import ImageSIZE = (75,75) SAVE_DIRECTORY = 'thumbs'def get_image_paths(folder):return (os.path.join(folder, f) for f in os.listdir(folder) if 'jpeg' in f)def create_thumbnail(filename): im = Image.open(filename)im.thumbnail(SIZE, Image.ANTIALIAS)base, fname = os.path.split(filename) save_path = os.path.join(base, SAVE_DIRECTORY, fname)im.save(save_path)if __name__ == '__main__':folder = os.path.abspath('11_18_2013_R000_IQM_Big_Sur_Mon__e10d1958e7b766c3e840')os.mkdir(os.path.join(folder, SAVE_DIRECTORY))images = get_image_paths(folder)for image in images: create_thumbnail(image)?
示例代碼中用了一些技巧,但大體上是:向程序傳入一個目錄,從目錄中獲取所有圖片,然后創建縮略圖,并將縮略圖存放到各自的目錄中。
在我的機器上,這個程序處理大約6000張圖片,花費27.9秒。
如果使用一個并行的map調用來替換for循環:
from multiprocessing import Pool from PIL import ImageSIZE = (75,75) SAVE_DIRECTORY = 'thumbs'def get_image_paths(folder):return (os.path.join(folder, f) for f in os.listdir(folder) if 'jpeg' in f)def create_thumbnail(filename): im = Image.open(filename)im.thumbnail(SIZE, Image.ANTIALIAS)base, fname = os.path.split(filename) save_path = os.path.join(base, SAVE_DIRECTORY, fname)im.save(save_path)if __name__ == '__main__':folder = os.path.abspath('11_18_2013_R000_IQM_Big_Sur_Mon__e10d1958e7b766c3e840')os.mkdir(os.path.join(folder, SAVE_DIRECTORY))images = get_image_paths(folder)pool = Pool()pool.map(create_thumbnail, images)pool.close() pool.join()?
5.6秒!
僅修改幾行代碼就能得到巨大的速度提升。這個程序的生產環境版本通過切分CPU密集型工作和IO密集型工作并分配到各自的進程和線程(通常是死鎖代碼的一個因素),獲得更快的速度。然而,由于map性質清晰明確,無需手動管理線程,以干凈、可靠、易于調試的方式混合匹配兩者(譯注:這里的“兩者”是指什么?CPU密集型工作和IO密集型工作?),也是相當容易的。
就是這樣了。(幾乎)一行式并行解決方案。
?
轉自:http://blog.xiayf.cn/2015/09/11/parallelism-in-one-line/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
?
轉載于:https://www.cnblogs.com/wangxusummer/p/4835929.html
總結
以上是生活随笔為你收集整理的Python Map 并行的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS字体
- 下一篇: DirectX 开发环境配置