如何在共享内存缓冲区上构建原地Python Dict?
问题
我正在使用multiprocessing.shared_memory在多个MPI进程间共享字典。将字典序列化为字节并复制到共享内存中的操作很简单:
import pickle from multiprocessing import shared_memory my_dict = {'k': 'v'} raw_dict = pickle.dumps(my_dict) dict_size = len(raw_dict) # 创建共享内存 shm = shared_memory.SharedMemory(create=True, size=dict_size) shm_name = shm.name shm.buf[:dict_size] = raw_dict
但如何无需复制内存将其转换回字典却让人困惑:
# 通过名称获取已创建的共享内存 shm = shared_memory.SharedMemory(name=shm_name) # 这段代码无法运行 deserialized_dict = dict(shm.buf)
请问Python中是否存在在缓冲区上原地构建对象的方法?
以下是完整代码供参考:
import numpy as np from mpi4py import MPI from multiprocessing import shared_memory, resource_tracker comm = MPI.COMM_WORLD rank = comm.Get_rank() size = comm.Get_size() if rank == 0: my_dict = {'k': 'v'} raw_dict = pickle.dumps(my_dict) dict_size = len(raw_dict) shm = shared_memory.SharedMemory(create=True, size=dict_size) shm.buf[:dict_size] = raw_dict comm.bcast(shm.name, root=0) else: shm_name = comm.bcast(None, root=0) print(rank, shm_name) shm = shared_memory.SharedMemory(name=shm_name) # 无法运行的代码 deserialized_dict = dict(shm.buf) # 这段代码会创建新内存来构建字典 # deserialized_dict = pickle.loads(shm.buf) print(rank, deserialized_dict) comm.Barrier() if rank == 0: shm.close() shm.unlink() else: shm.close() resource_tracker.unregister(shm._name, 'shared_memory')
解决方案
首先明确:Python原生dict无法直接在共享内存缓冲区上原地构建,核心原因有两点:
- Python内置对象(包括
dict)的内存布局由解释器管控,依赖Python私有堆内存,无法直接映射到外部共享内存区域。 - 所有反序列化方法(如
pickle.loads)本质上都会在进程私有内存中重建对象,必然会复制共享内存中的字节数据——这是Python对象模型决定的,无法完全规避。
不过可以通过两种思路优化你的需求:
1. 使用共享内存原生的类字典结构
如果允许替换原生dict,可以直接用基于共享内存实现的类字典工具,避免序列化/反序列化的复制开销:
- 第三方库如
shared_dict:直接在共享内存中维护键值对结构,进程间访问无需复制数据。
示例代码:
# 进程0创建共享字典 from shared_dict import SharedDict sh_dict = SharedDict(create=True, name='my_shared_dict') sh_dict['k'] = 'v' # 其他进程直接访问共享字典 sh_dict = SharedDict(name='my_shared_dict') print(sh_dict['k']) # 直接读取共享内存数据,无额外复制
2. 最小化序列化的复制开销
如果必须使用原生dict,可以通过以下方式降低复制成本:
- 用更高效的序列化库(如
msgpack、ujson)替代pickle,它们生成的序列化结果更小,复制开销更低。 - 直接传递
memoryview切片给反序列化函数:shm.buf是memoryview对象,pickle.loads可以直接读取它,无需先转换为bytes(这已经是当前能做到的最小复制方式)。
修改后的反序列化代码:
shm = shared_memory.SharedMemory(name=shm_name) # 直接用memoryview切片反序列化,避免额外内存复制 deserialized_dict = pickle.loads(shm.buf[:dict_size])
注:这里的"无额外复制"指的是跳过了memoryview转bytes的步骤,但创建dict对象本身需要在私有内存中分配空间存储键值对,这是Python对象模型无法避免的。
为什么dict(shm.buf)无法运行
shm.buf是memoryview类型,dict()构造函数需要的是可迭代的键值对,而非字节数据,这段代码本身就不符合语法逻辑,自然无法执行。
内容的提问来源于Stack Exchange,提问作者Rossil
相关产品推荐
相关产品推荐

