python - 如何在我的 8G DDR3 RAM 中托管大型列表?
问题描述
我是 python 新手,只是想知道内存分配是如何工作的。事实证明,衡量存储变量大小的一种方法是使用sys.getsizeof(x)
它,它将返回x
内存中占用的字节数,对吧?以下是示例代码:
import struct
import sys
x = struct.pack('<L', 0xffffffff)
print(len(x))
print(sys.getsizeof(x))
这使:
4
37
我刚刚创建的变量x
是一个 4 字节的字符串,这里出现了第一个问题。为什么分配给一个4字节字符串的内存是37
字节?是不是多余的空间太多了?
当我开始创建一个 2 * 4 字节字符串的列表时,情况变得更加复杂。下面你会发现另外几行:
import struct
import sys
k = 2
rng = range(0, k)
x = [b''] * k
for i in rng:
x[i] = struct.pack('<L', 0xffffffff)
print(len(x))
print(len(x[0]))
print(sys.getsizeof(x))
print(sys.getsizeof(x[0]))
我从中得到:
2
4
80
37
另一个问题是,为什么当我将两个 4 字节字符串存储在一个列表中时,分配给它们的内存总和不等于它们单独大小的总和?!那就是37 + 37 != 80
。那些额外的 6 个字节是做什么用的?
让我们放大k
到10000
,前面的代码给出:
10000
4
80064
37
将独奏尺寸与整体尺寸进行比较时,差异显着增加37 * 10000 = 370000 != 80064
:看起来列表中的每个项目现在都在占用80064/10000 = 8.0064
字节。听起来可行,但我仍然无法解决之前显示的冲突。
毕竟,我的主要问题是,当我k
想到0xffffffff
并期望获得一个大小列表时,~ 8 * 0xffffffff = 34359738360
我实际上遇到了 MemoryError 异常。有什么方法可以消除非关键内存空间,以便我的 8G DDR3 RAM 可以承载这个变量x
?
解决方案
为什么分配给 4 字节字符串的内存是 37 字节?是不是多余的空间太多了?
Python 中的所有对象在每个对象的基础上都有一些“倾斜”。请注意,对于bytes
可能所有不可变的 stdlib 类型,此填充(此处为 33 个字节)与对象的长度无关:
from sys import getsizeof as gso
print(gso(b'x'*1000) - gso(b''))
# 1000
请注意,这与以下内容不同:
print(gso([b'x']*1000) - gso(b''))
# 8031
在前者中,您正在制作bytes
1000 个 x 的对象。
在后者中,您正在制作一个 1000 字节对象的列表。重要的区别在于,在后者中,您 (a) 将字节对象复制 1000 次,并合并列表容器的大小。(差异的原因只有〜8,000而不是〜34,000(即每个元素8个字节而不是每个元素34个字节(= sizeof(b'x')
)接下来。)
让我们谈谈容器:
print(gso([b'x'*100,]) - gso([]))
getsizeof
在这里,我们打印一个元素列表(一个 100 字节长的byte
对象)和一个空列表之间的差异。我们有效地去除了容器的大小。
我们可能期望这等于getsizeof(b'x' * 100)
。
它不是。
的结果print(gso([b'x'*100,]) - gso([]))
是 8 个字节(在我的机器上),这是因为列表只包含对基础对象的引用/指针,而这 8 个字节就是指向列表单个元素的指针。
那是 37 + 37 != 80。那额外的 6 个字节是干什么用的?
让我们做同样的事情并通过减去容器的大小来查看净大小:
x = [b'\xff\xff\xff\xff', b'\xff\xff\xff\xff']
print(gso(x[0]) - gso(b'')) # 4
print(gso(x) - gso([])) # 16
首先,返回的 4 与我提供的第一个示例中返回的 1000 一样,每个字节一个。(len(x[0])
是 4)。
在第二个中,每个对子列表的引用为 8 个字节。它与这些子列表的内容无关:
N = 1000
x = [b'x'] * N
y = [b'xxxx'] * N
print(gso(x) == gso(y))
# True
但是,虽然可变容器似乎没有固定的“斜率”:
lst = []
for _ in range(100):
lst.append('-')
x = list(lst)
slop = gso(x) - (8 * len(x))
print({"len": len(x), "slop": slop})
输出:
{'len':1,'slop':88} {'len': 2, 'slop': 88} {'len':3,'slop':88} {'len':4,'slop':88} {'len': 5, 'slop': 88} {'len':6,'slop':88} {'len': 7, 'slop': 88} {'len': 8, 'slop': 96} {'len':9,'slop':120} {'len':10,'slop':120} {'len':11,'slop':120} {'len':12,'slop':120} {'len':13,'slop':120} {'len':14,'slop':120} {'len':15,'slop':120} {'len':16,'slop':128} {'len':17,'slop':128} {'len':18,'slop':128} {'len':19,'slop':128} {'len':20,'slop':128} {'len':21,'slop':128} {'len':22,'slop':128} {'len':23,'slop':128} {'len':24,'slop':136} ...
...不可变容器可以:
lst = []
for _ in range(100):
lst.append('-')
x = tuple(lst)
slop = gso(x) - (8 * len(x))
print({"len": len(x), "slop": slop})
{'len':1,'slop':48} {'len':2,'slop':48} {'len':3,'slop':48} {'len':4,'slop':48} {'len': 5, 'slop': 48} {'len':6,'slop':48} {'len': 7, 'slop': 48} {'len':8,'slop':48} {'len':9,'slop':48} {'len':10,'slop':48} {'len':11,'slop':48} {'len':12,'slop':48} {'len':13,'slop':48} {'len':14,'slop':48} ...
有没有办法消除非关键内存空间,以便我的 8G DDR3 RAM 可以承载这个变量 x?
首先,请记住容器的大小不会反映 Python 使用的全部内存量。每个元素约 8 个字节是指针的大小,每个元素将消耗额外的 37(或其他)字节(无实习或类似优化)。
但好消息是,您不太可能同时不需要整个列表。如果您只是构建一个要迭代的列表,则使用 for 循环或生成器函数一次生成一个元素。
或者一次生成一个块,处理它,然后继续,让垃圾收集器清理不再使用的内存。
另一件有趣的事情要指出
N = 1000
x = [b'x' for _ in range(N)]
y = [b'x'] * N
print(x == y) # True
print(gso(x) == gso(y)) # False
(这可能是由于先验y
已知的大小,而大小不是,并且随着它的增长而调整了大小)。x
推荐阅读
- python - 比较多行python
- typescript - 在 RxJS 6 中为 Single、Maybe 和 Completable 创建类
- composer-php - 加载私有 git 仓库的 composer 依赖
- javascript - JSP 无法使用 webservlet doGet 使 AJAX 工作
- python - 从远程计算机使用 pyodbc 插入 SQL
- mongodb - 从一个聚合中的两个字段查找和分组
- instance - Godot引擎:删除场景实例而不释放整个场景
- c - 两个进程之间的共享内存
- r - 在R中等分单个空间多边形
- node.js - Nodejs vm2 - 如何导入脚本