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

能否以无额外空间开销的原始格式存储字节?

能否以无额外空间开销的原始格式存储字节?

首先得说清楚:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 17:35:27