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