You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

序列化共享内存的ndarray元组占用双倍空间,如何优化?

问题描述

给定以下代码:

import numpy as np
import pickle

a = np.random.rand(4000)
b = a.T

assert np.shares_memory(a, b)

tup = (a, b)
s = pickle.dumps(tup)
print(len(s)) # 64187

s1 = pickle.dumps(a)
print(len(s1)) # 32151

由于a和b共享同一块内存,理论上序列化包含二者的元组时,结果大小应接近单独序列化其中一个的大小,但实际结果是两者大小之和。如何实现只序列化一份内存数据的目标?

解决方案

方法1:利用pickle备忘录机制手动复用引用

pickle的备忘录(memo)可以记录已序列化的对象,我们可以自定义序列化逻辑,让共享内存的数组视图复用已序列化的base数组,避免重复存储:

import numpy as np
import pickle

def pickle_shared_arrays(obj):
    memo = {}
    def reducer(item):
        if isinstance(item, np.ndarray):
            base = item.base if item.base is not None else item
            if base in memo:
                # 返回还原视图的逻辑,根据原数组生成转置/视图
                return (lambda arr: arr.T if item.flags.f_contiguous else arr, (memo[base],))
            memo[base] = item
        return pickle._reduce(item)
    
    return pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL, reducer=reducer)

# 测试
a = np.random.rand(4000)
b = a.T
tup = (a, b)
s = pickle_shared_arrays(tup)
print(len(s))  # 输出接近32151,与单独序列化a的大小一致

# 验证还原后的内存共享
loaded_tup = pickle.loads(s)
assert np.shares_memory(loaded_tup[0], loaded_tup[1])

方法2:使用numpy.savez(纯numpy数组场景)

如果你的数据仅包含numpy数组,numpy.savez会自动检测共享内存的数组,只存储一份原始数据:

import numpy as np

a = np.random.rand(4000)
b = a.T
np.savez('shared_data.npz', a=a, b=b)

# 读取验证
loaded_data = np.load('shared_data.npz')
assert np.shares_memory(loaded_data['a'], loaded_data['b'])

生成的文件大小与单独存储a的大小接近。

方法3:修改numpy数组的pickle还原逻辑

通过全局修改np.ndarray.__reduce__方法,让数组视图只序列化base数组和视图参数,还原时重新生成视图:

import numpy as np
import pickle

# 保存原始reduce方法
original_reduce = np.ndarray.__reduce__

def shared_reduce(arr):
    if arr.base is not None:
        # 对于视图,返回生成视图的构造参数
        return (np.ndarray, (arr.shape, arr.dtype, arr.base, arr.offset, arr.strides))
    return original_reduce(arr)

np.ndarray.__reduce__ = shared_reduce

# 测试
a = np.random.rand(4000)
b = a.T
tup = (a, b)
s = pickle.dumps(tup, protocol=pickle.HIGHEST_PROTOCOL)
print(len(s))  # 输出接近32151

# 验证还原结果
loaded_tup = pickle.loads(s)
assert np.shares_memory(loaded_tup[0], loaded_tup[1])

# 恢复原始方法,避免影响其他逻辑
np.ndarray.__reduce__ = original_reduce

注意:该方法会全局修改numpy数组的序列化行为,需谨慎使用。

内容的提问来源于stack exchange,提问作者吴慈霆

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 09:07:45