我试图了解如何与多处理共享只读对象

I am trying to understand how to share read-only objects with multiprocessing

我正在尝试了解如何与多处理共享只读对象。共享 bigset 当它是一个全局变量时工作正常:

from multiprocessing import Pool

bigset = set(xrange(pow(10, 7)))

def worker(x):
  return x in bigset

def main():
  pool = Pool(5)
  print all(pool.imap(worker, xrange(pow(10, 6))))
  pool.close()
  pool.join()

if __name__ == '__main__':
  main()

htop 显示父进程使用 100% CPU 和 0.8% 内存,而工作负载平均分配给五个子进程:每个进程使用 10% CPU和 0.8% 的内存。一切都很好。

但是如果我将 bigset 移到 main 中,数字开始变得疯狂:

from multiprocessing import Pool
from functools import partial    

def worker(x, l):
  return x in l

def main():
  bigset = set(xrange(pow(10, 7)))
  _worker = partial(worker, l=bigset)
  pool = Pool(5)
  print all(pool.imap(_worker, xrange(pow(10, 6))))
  pool.close()
  pool.join()

if __name__ == '__main__':
  main()

现在 htop 显示 2 或 3 个进程在 50% 和 80% 之间上下跳跃 CPU,而其余进程使用不到 10% CPU。虽然父进程仍然使用 0.8% 的内存,但现在所有子进程都使用 1.9% 的内存。

发生了什么事?

当您将 bigset 作为参数传递时,它由父进程 pickle 并由子进程 unplickled。[1][2]

对大型集合进行 pickling 和 unpickling 需要很多时间。这就解释了为什么你看到很少有进程在做他们的工作:父进程必须 pickle 很多大对象,而子进程必须等待它。父进程是一个瓶颈。

Pickling 参数意味着必须将参数发送到进程。从一个进程向另一个进程发送数据需要系统调用,这就是为什么您没有看到用户 space 代码使用 100% CPU 的原因。部分 CPU 时间花费在内核上 space.[3]

腌制对象并将它们发送到子进程也意味着:1.你需要内存来存放腌制缓冲区; 2. 每个子进程得到一份bigset。这就是您看到内存使用量增加的原因。

相反,当 bigset 是一个全局变量时,它不会被发送到任何地方(除非您使用 fork 以外的 start method。它只是由子进程继承 as-is,使用 fork().

的通常 copy-on-write 规则

脚注:

  1. 如果您不知道 "pickling" 是什么意思:pickle 是标准 Python 协议之一,用于转换任意 Python 对象往返于一个字节序列。

  2. imap() & 公司。在幕后使用 queues,队列由 pickling/unpickling 个对象工作。

  3. 我尝试了 运行 您的代码(使用 all(pool.imap(_worker, xrange(100))) 以使过程更快),我得到:2 分钟的用户时间,13 秒的系统时间。这几乎是系统时间的 10%。