比較好奇python對於多進程中copy on write機制的實際使用方式。目前從實驗結果來看,python 使用multiprocessing來建立多進程時,無論資料是否不會被更改,子進程都會複製父進程的狀態(記憶體空間資料等)。所以如果主進程耗的資源較多時,不小心就會造成不必要的大量的記憶體複製,從而可能導致記憶體爆滿的情況。
樣本舉個例子,假設主進程讀取了一個大檔案對象的所有行,然後通過multiprocessing建立背景工作處理序,並迴圈地將每一行資料交給背景工作處理序來處理:
def parse_lines(args): #working ...def main_logic(): f = open(filename , 'r') lines = f.readlines() f.close() pool = multiprocessing.Pool(processes==4) rel = pool.map(parse_lines , itertools.izip(lines , itertools.repeat(second_args)) , int(len(lines)/4)) pool.close() pool.join()
以下是top及ps結果:
(四個子進程)
(父進程及四個子進程)
由上兩張圖可以看出父進程及子進程都各自佔用了1.4G左右的記憶體空間。而大部分記憶體空間儲存的是讀資料lines,所以這樣的記憶體開銷太浪費。
最佳化計劃
計劃1:通過記憶體共用來減少記憶體的開銷。
關於記憶體共用的資料可參考:http://www.360doc.com/content/11/0408/23/4910_108289025.shtml
計劃2: 主進程不再讀取檔案對象,交給每個背景工作處理序去讀取檔案中的相應部分。
改進代碼:
def line_count(file_name): count = -1 #讓空檔案的行號顯示0 for count,line in enumerate(open(file_name)): pass #enumerate格式化成了元組,count就是行號,因為從0開始要+1 return count+1def parse_lines(args): f = open(args[0] , 'r') lines = f.readlines()[args[1]:args[2]] #read some lines f.close() #workingdef main_logic(filename,process_num): line_count = line_count(filename) avg_len = int(line_count/process_num) left_cnt = line_count%process_num; pool = multiprocessing.Pool(processes=process_num) for i in xrange(0,process_num): ext_cnt = (i>=process_num-1 and [left_cnt] or [0])[0] st_line = i*avg_len pool.apply_async(parse_lines, ((filename, st_line, st_line+avg_len+ext_cnt),)) #指定進程讀某幾行資料 pool.close() pool.join()
再次用top或者ps來查看進程的記憶體使用量情況:
(四個子進程)
(父進程及四個子進程)
小結
對比兩次的記憶體使用量情況,改進代碼後父進程及子進程所佔用的記憶體明顯減少;所有記憶體佔用相當於原來的一半,這就是減少記憶體複製的效果。
實驗暫時先進行到此,關於記憶體使用量這方面還有不少最佳化方法和空間,稍後繼續研究。