通过multiprocessing.pool的Python3并行代码比顺序代码慢

时间:2018-10-04 19:50:00

标签: python python-3.x

这是我为并行创建字典而编写的一些代码

parallelize.py

if __name__ == "__main__":
  import time

  from multiprocessing import Pool

  def assign_dict(alist):
    return {x:x for x in alist}


  my_dict = {}
  size = 10000000
  threshold=10000000
  my_list=list(range(size))

  start=time.time()
  my_dict=assign_dict(my_list)
  end=time.time()
  print("check seq",end-start, " sec")

  my_dict = {}

  chunks = [my_list[i*threshold:(i+1)*threshold] for i in range(int(size/threshold))]
  process_count = 7   
  pool = Pool(processes=process_count)

  start = time.time()
  inter_list = pool.map_async(assign_dict, chunks)
  inter_list.wait()
  inter_list=inter_list.get()

  for some_dict in inter_list:
    print("Combining...", time.time()-start, " sec elapsed")
    my_dict = {**my_dict, **some_dict}

  print("check 152002 as key ",my_dict[152002])
  end=time.time()
  print("check parallel",end-start, " sec")

这是大小为100万,阈值为100万的输出

check seq 0.6559352874755859  sec
Combining... 4.751460790634155  sec elapsed
check 152002 as key  152002
check parallel 5.064720869064331  sec

此处输出的大小为1000万,阈值为100万

check seq 0.668889045715332  sec
Combining... 1.6871337890625
Combining... 1.7269806861877441
Combining... 1.860083818435669
Combining... 2.0794677734375
Combining... 2.266465663909912
Combining... 2.47836971282959
Combining... 2.8915648460388184
Combining... 3.2443037033081055
Combining... 3.6063129901885986
Combining... 3.9933629035949707
check 115202 as key  1152002
check parallel 4.406447887420654  sec

这是大小为1亿,阈值为10百万的输出,这里最糟糕的部分甚至是在合并之前,与map_async相比,55 secs依旧需要19 secs

check seq 19.182615041732788  sec
Combining... 55.18172788619995
Combining... 56.38586497306824
Combining... 58.534785747528076
Combining... 61.805513858795166
Combining... 64.75091290473938
Combining... 71.34392070770264
Combining... 76.02847385406494
Combining... 81.2545096874237
Combining... 87.75674867630005
Combining... 109.01232576370239
check 115202 as key  1152002
check parallel 126.1939218044281  sec

尽管我尝试了sizethreshold的各种组合,但是带有pool的代码总是比较慢,因此,由于顺序版本运行非常快,因此阈值并不是太大。即使sizethreshold相同,带有pool的代码也要慢很多秒。

即使对于大小= 10亿的长期运行作业,并行化版本也比顺序执行慢得多,这意味着并行化没有任何好处。我有8个核心和16GB RAM,我正在运行MacOSX,甚至还验证了我的核心在活动监视器中并行运行以执行任务,但速度较慢。如图所示,合并阶段不会花费很多时间。在inter_list.get()命令结束时,并行部分已经完成。因此它不会干扰字典的组合。

任何人都可以将这段代码并行化以使其比Python 3中的顺序版本更快,或者至少可以帮助我理解为什么会这样吗?

1 个答案:

答案 0 :(得分:5)

您的多处理版本比顺序版本慢,因为需要进行进程间通信,以将Pool.map的结果从工作人员传回给工作人员所分叉的过程。

由于GIL,建议使用Python的多处理库来执行CPU密集型并行任务。但是,这意味着Pool中每个工作程序的虚拟内存地址空间是不同的,因此Pool.map的结果必须被序列化并在进程之间传递。因为Pool.map的结果是如此之大,所以这意味着您的程序要花费大量时间对答案进行序列化/反序列化并将它们在进程之间传递。在顺序版本中,只有一个进程,因此结果永远不需要序列化并在进程之间传递然后再反序列化,这可能就是在这种情况下运行速度更快的原因。

如果要避免出现此瓶颈,则将尝试使用Python的Shared Memory array来避免进程间的通信瓶颈,因为该数组将位于所有工作进程的相同虚拟地址空间中。如果您确实需要键值对映射,请查看Python的multiprocessing.Manager.dict

通常,Pool.map可以并行化一些不会产生大量数据的计算。