数据处理,遇到几千万上亿级别的,一次几十个小时,利用并行可以有效减少时长。
网上讲原理的很多。
主要放一下自己跑通的一个小demo,备忘。
Pool是最简单的。
pool.apply()可以并行一些不需要返回值的函数。
poo.apply_async() 可以获得返回值。
处理pandas数据,基本都是需要返回值的,上一个apply_async的例子。
原本需要十几个小时,4路并行后降低至4小时。
import multiprocessing
def get_userset_dict(idd,batch_items,train_csv):
userset_dict = {}
for item in tqdm(batch_items):
userset_dict[item] =set(train_csv[train_csv['item_id']==item]['buyer_admin_id'])
print(idd,'subprocess complete')
return userset_dict
pool = multiprocessing.Pool(processes=4)
result = []
for i in range(4):
batch_items= all_items[i*720000:(i+1)*720000]
result.append(pool.apply_async(get_userset_dict, (i,batch_items,train_csv)))
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
for p in multiprocessing.active_children():
print("child p.name: " + p.name + "\t p.id: " + str(p.pid))
pool.close()
pool.join()
print('all right!')
#-----------打印结果------------
The number of CPU is:12
child p.name: ForkPoolWorker-12 p.id: 5216
child p.name: ForkPoolWorker-11 p.id: 5215
child p.name: ForkPoolWorker-10 p.id: 5214
child p.name: ForkPoolWorker-9 p.id: 5213
100%|██████████| 652048/652048 [3:42:48<00:00, 48.78it/s]
3 subprocess complete
100%|██████████| 720000/720000 [3:59:55<00:00, 50.02it/s]
2 subprocess complete
100%|██████████| 720000/720000 [4:00:08<00:00, 49.97it/s]
1 subprocess complete
100%|██████████| 720000/720000 [4:00:19<00:00, 49.93it/s]
0 subprocess complete
all right!
现在理论上,result这个list里面有我们的返回值,如何获取呢?
#apply_async返回的是"返回值的对象"
#需要使用.get()方法进一步获得返回值
for d in result:
print(d,',',type(d.get()))
#<multiprocessing.pool.ApplyResult object at 0x7f2794a64518> , <class 'dict'>
#<multiprocessing.pool.ApplyResult object at 0x7f2794a0b780> , <class 'dict'>
#<multiprocessing.pool.ApplyResult object at 0x7f2794a0bb00> , <class 'dict'>
#<multiprocessing.pool.ApplyResult object at 0x7f28582eda20> , <class 'dict'>
#结果合并
userset_dict ={}
for d in result:
userset_dict.update(d.get())
pickle.dump(userset_dict,open('./data/userset_dict.pkl','wb'))
print('saved')
注意,我没研究过.get()的次序问题。
因为这个需求只用返回dict,对数据之间的次序关系无所谓。
不同sub-process的结束时间不同,有可能append的次序也不同。
对次序有要求的任务,要小心。
下面这种理论上功能更高级,还可以开管道,但我不知道怎么拿到返回值。
import multiprocessing
train_csv = pickle.load(open('./data/train_csv.pkl','rb'))
all_items = sorted(list(set(train_csv['item_id'])))
def get_userset_dict(idd,batch_items,train_csv):
userset_dict = {}
for item in tqdm(batch_items):
userset_dict[item] =set(train_csv[train_csv['item_id']==item]['buyer_admin_id'])
print(idd,'subprocess complete')
return userset_dict
p_list = []
for i in range(4):
batch_items= all_items[i*720000:(i+1)*720000]
p_list.append(multiprocessing.Process(target=get_userset_dict, args=(i,batch_items,train_csv)))
ts = time.time()
for p in p_list:
p.start()
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
for p in multiprocessing.active_children():
print("child p.name: " + p.name + "\t p.id: " + str(p.pid))
for p in p_list:
p.join()
print(time.time()-ts)
补坑说明
python的multiprocess.pool不共享内存,传入的值如果改变就相当于复制了好几分。(从我监控内存变化来看是这样的)
需要确保任务运行过程中的临时变量小于总内存,不然一损具损,全部宕机。
而且,在pool.apply_async调用的函数内,使用pickle.dump有概率出现莫名其妙的key error等错误,没找到原因。(不使用pool直接跑目标函数不会报错)