能否以无额外空间开销的原始格式存储字节?
能否以无额外空间开销的原始格式存储字节?
首先得说清楚:在Python的对象模型里,完全消除所有额外内存开销是做不到的——因为你要在堆里存储10000个可比较的元素,每个元素都得是Python对象,而每个对象都有基础的内存开销(比如对象头、引用计数这些)。但我们可以把开销降到非常接近你期望的120000字节,远低于现在的565k。
问题根源
你现在用独立的bytes对象存储每个子串,每个bytes哪怕只有12字节数据,在64位CPython里都要占用约48字节的内存(对象头、长度字段、哈希值,再加上实际数据的对齐空间)。10000个这样的对象加起来,额外开销就有4810000=480k,再加上列表里存储这些对象的指针(810000=80k),总开销自然就到了560k左右。
解决方案:共享缓冲区+轻量级包装类
核心思路是:把所有子串的原始字节数据一次性存在一个共享的大bytes缓冲区里,这样就避免了每个小bytes对象的额外开销;然后堆里存储的是每个子串在缓冲区里的起始偏移量,用一个轻量级类包装偏移量,实现正确的字典序比较逻辑。
具体代码如下:
from heapq import heappush, heappop from pympler.asizeof import asizeof class ByteSliceRef: __slots__ = ('offset',) # 用__slots__禁用实例的__dict__,大幅减少内存开销 _item_len = 12 # 每个子串的固定长度,不用存在实例里 def __init__(self, offset): self.offset = offset def __lt__(self, other): # 从共享缓冲区中取出对应片段做字典序比较 return buffer[self.offset:self.offset+self._item_len] < buffer[other.offset:other.offset+self._item_len] # 初始化共享缓冲区和堆列表 buffer = b'' heap = [] for _ in range(10_000): item = b"abcdabcdabcd" # 等价于("abcd"*3).encode('latin1') offset = len(buffer) buffer += item heappush(heap, ByteSliceRef(offset)) # 查看内存占用 print(f"堆列表内存:{asizeof(heap)} bytes") print(f"共享缓冲区内存:{asizeof(buffer)} bytes") print(f"总内存占用:{asizeof(heap) + asizeof(buffer)} bytes")
效果说明
- 共享缓冲区
buffer的内存开销几乎就是原始数据的120000字节,再加上几十字节的对象头开销,总共约120040字节。 - 每个
ByteSliceRef实例因为用了__slots__,在64位CPython里每个只占用约32字节(远小于独立bytes对象的48字节),10000个实例就是320000字节左右。 - 堆列表本身的开销主要是存储10000个对象指针,约80000字节。
总内存占用会比你当前的565k低不少,虽然没法完全达到120000(这是Python对象模型的限制),但已经是兼顾易用性和内存效率的最优方案了。
更极致的尝试(可选)
如果你的所有子串长度完全固定,还可以把偏移量直接存在一个array.array('I')里(用4字节的无符号整数,因为120000<2^32),但这样就不能直接用标准库的heapq了——因为heapq不支持自定义比较函数,你得自己实现堆的操作逻辑,复杂度会高很多,性价比不高。
备注:内容来源于stack exchange,提问作者Simd
相关产品推荐
相关产品推荐

