如何在我的8G DDR3 RAM中托管一个大型列表?

时间:2018-11-17 06:29:01

标签: python python-3.x memory

我是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

1 个答案:

答案 0 :(得分:5)

  

为什么分配给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

在前一种情况下,您要制作一个{x}的1000 x对象。

在后者中,将列出1000个字节的对象。重要的区别在于,在后者中,您(a)将字节对象复制1000次,合并列表容器的大小。 (接下来的差异之所以只有8,000,而不是34,000(即每个元素8个字节,而不是每个元素34个字节(= {bytes))。

让我们来谈谈容器:

sizeof(b'x')

在这里,我们打印一个元素列表(长100字节的print(gso([b'x'*100,]) - gso([])) 对象)的getsizeof与一个空列表之间的差异。我们有效地taring超出了容器的大小。

我们可能期望它等于byte

不是。

getsizeof(b'x' * 100)的结果是8个字节(在我的机器上),这是因为列表仅包含对基础对象的引用/指针,而这8个字节仅是-指向列表中单个元素的指针

  

那是37 + 37!=80。那额外的6个字节是做什么用的?

让我们做同样的事情,并通过减去容器的大小来查看净大小:

print(gso([b'x'*100,]) - gso([]))

在第一个示例中,返回的4就像我在第一个示例中提供的1000一样,每个字节一个。 (x = [b'\xff\xff\xff\xff', b'\xff\xff\xff\xff'] print(gso(x[0]) - gso(b'')) # 4 print(gso(x) - gso([])) # 16 是4)。

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

len(x[0])

但是可变容器似乎没有固定的“倾斜”:

N = 1000
x = [b'x']    * N
y = [b'xxxx'] * N
print(gso(x) == gso(y))
# True

输出:

{'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 = list(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个(或任何其他大小)字节(没有内部迭代或类似的优化)。

但是好消息是您不太可能同时不需要 entire 列表。如果您只是要构建要迭代的列表,则可以使用for循环或生成器函数一次生成一个元素。

或者一次生成一个大块,进行处理,然后继续,让垃圾回收器清理不再使用的内存。


还有一件有趣的事情要指出

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

    slop = gso(x) - (8 * len(x))
    print({"len": len(x), "slop": slop})

(这很可能是由于N = 1000 x = [b'x' for _ in range(N)] y = [b'x'] * N print(x == y) # True print(gso(x) == gso(y)) # False 的大小先验已知的,而y的大小却没有,并且随着大小的增长而调整了大小。)< / p>