Python:使用多进程共享大字典

9 投票
4 回答
5554 浏览
提问于 2025-04-16 09:03

我正在处理非常大量的数据,这些数据存储在一个字典里,并且我使用了多进程。基本上,我的操作就是加载一些存储在字典里的签名,然后用这些签名构建一个共享的字典对象(通过Manager.dict()得到的“代理”对象),接着把这个代理作为参数传递给需要在多进程中执行的函数。

为了更清楚:

signatures = dict()
load_signatures(signatures)
[...]
manager = Manager()
signaturesProxy = manager.dict(signatures)
[...]
result = pool.map ( myfunction , [ signaturesProxy ]*NUM_CORES )

现在,如果signatures的条目少于200万,所有操作都很顺利。不过,我需要处理一个有580万键的字典(把signatures以二进制格式保存会生成一个4.8GB的文件)。在这种情况下,创建代理对象时进程就崩溃了:

Traceback (most recent call last):
  File "matrix.py", line 617, in <module>
signaturesProxy = manager.dict(signatures)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 634, in temp
token, exp = self._create(typeid, *args, **kwds)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 534, in _create
id, exposed = dispatch(conn, None, 'create', (typeid,)+args, kwds)
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 79, in dispatch
raise convert_to_error(kind, result)
multiprocessing.managers.RemoteError: 
---------------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.6/multiprocessing/managers.py", line 173, in handle_request
    request = c.recv()
EOFError
---------------------------------------------------------------------------

我知道这个数据结构很大,但我在一台有32GB内存的机器上工作。通过运行top命令,我看到在加载签名后,进程占用了7GB的内存。然后它开始构建代理对象,内存使用量上升到大约17GB,但从来没有接近32GB。此时,内存使用量开始迅速下降,进程就以上述错误终止了。所以我猜这不是因为内存不足的问题……

有没有什么想法或建议?

谢谢,

Davide

4 个回答

2

为了节省时间,避免调试系统级别的问题,你可以把你那580万条记录的字典分成三组,每组大约200万条,然后运行三次这个任务。

6

你为什么不试试用数据库呢?数据库不受限于可寻址的物理内存,而且在多线程或多进程使用时也很安全。

-2

如果字典是只读的,大多数操作系统下你就不需要代理对象了。

在启动工作进程之前,先加载好字典,并把它放在一个可以被访问的地方;最简单的方式就是放在模块的全局范围内。这样,工作进程就能读取到这些字典。

from multiprocessing import Pool

buf = ""

def f(x):
    buf.find("x")
    return 0

if __name__ == '__main__':
    buf = "a" * 1024 * 1024 * 1024
    pool = Pool(processes=1)
    result = pool.apply_async(f, [10])
    print result.get(timeout=5)

这样总共只会使用1GB的内存,而不是每个进程都用1GB,因为现代操作系统会在创建进程时对数据进行“写时复制”。只要记住,其他工作进程看不到你对数据的修改,当然你修改的数据会占用内存。

这会使用一些内存:每个对象的引用计数所在的页面会被修改,所以会被分配内存。是否重要就看你处理的数据了。

这个方法在任何实现了普通分叉的操作系统上都能用。它在Windows上不行;因为Windows的进程模型比较特殊,需要为每个工作进程重新启动整个进程,所以在共享数据方面表现不太好。

撰写回答