如何在8G DDR3 RAM中托管大型列表?

Pou*_*uJa 2 python memory python-3.x

我是python的新手,只是想知道内存分配是如何工作的.事实证明,测量存储变量大小的一种方法是使用sys.getsizeof(x)它,它将返回x内存中占用的字节数,对吧?以下是示例代码:

import struct
import sys

x = struct.pack('<L', 0xffffffff)
print(len(x))
print(sys.getsizeof(x))
Run Code Online (Sandbox Code Playgroud)

这使:

4
37
Run Code Online (Sandbox Code Playgroud)

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]))
Run Code Online (Sandbox Code Playgroud)

从中得到:

2
4
80
37
Run Code Online (Sandbox Code Playgroud)

另一个问题是,为什么当我在列表中存储两个4字节字符串时,分配给它们的内存总和不等于它们的独奏大小的总和?!那是37 + 37 != 80.那些额外的6个字节是什么?

让我们放大k10000,以前的代码得到:

10000
4
80064
37
Run Code Online (Sandbox Code Playgroud)

在将独奏大小与整体进行比较时,差异显着增加:37 * 10000 = 370000 != 80064.看起来列表中的每个项目现在都占用了80064/10000 = 8.0064字节.听起来很可行,但我仍然无法解决之前显示的冲突.

毕竟,我的主要问题是,当我上升k0xffffffff和期望得到的尺寸列表~ 8 * 0xffffffff = 34359738360其实我遇到的MemoryError的一个例外.有没有办法消除非关键内存空间,以便我的8G DDR3 RAM可以托管这个变量x

jed*_*rds 5

为什么分配给4字节字符串的内存是37字节?这不是太多额外的空间吗?

Python中的所有对象在每个对象的基础上都有一些"slop".请注意,对于bytes且可能是所有不可变的stdlib类型,此填充(此处为33个字节)与对象的长度无关:

from sys import getsizeof as gso
print(gso(b'x'*1000) - gso(b''))
# 1000
Run Code Online (Sandbox Code Playgroud)

请注意,这是不一样的:

print(gso([b'x']*1000) - gso(b''))
# 8031
Run Code Online (Sandbox Code Playgroud)

在前者中,你正在制作一个bytes1000 x的物体.

在后者中,您将创建一个1000字节对象的列表.重要的区别在于,在后者中,您(a)将字节对象复制1000次,合并列表容器的大小.(差异的原因仅为~8,000而不是~34,000(即每个元素8个字节而不是每个元素34个字节(= sizeof(b'x'))).

让我们谈谈容器:

print(gso([b'x'*100,]) - gso([]))
Run Code Online (Sandbox Code Playgroud)

这里我们打印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
Run Code Online (Sandbox Code Playgroud)

在第一个中,返回的4与我提供的第一个示例中返回的1000一样,每个字节一个.(len(x[0])是4).

在第二个中,每个引用到子列表是8个字节.它与这些子列表的内容无关:

N = 1000
x = [b'x']    * N
y = [b'xxxx'] * N
print(gso(x) == gso(y))
# True
Run Code Online (Sandbox Code Playgroud)

但是,虽然可变容器似乎没有固定的"slop":

lst = []
for _ in range(100):
    lst.append('-')
    x = list(lst)

    slop = gso(x) - (8 * len(x))
    print({"len": len(x), "slop": slop})
Run Code Online (Sandbox Code Playgroud)

输出:

{'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})
Run Code Online (Sandbox Code Playgroud)
{'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
Run Code Online (Sandbox Code Playgroud)

(这可能是由于先验y已知的大小,而大小不是并且随着它的增长而调整大小).x