为什么Python列表在内存中的大小与文档不符?

1 投票
1 回答
58 浏览
提问于 2025-04-13 02:11

我一直在努力理解Python是如何为列表分配内存的,特别是在通过添加元素来扩展列表时。这个问题很好地介绍了基础知识,并解释了随着列表长度增加,内存分配的增量是如何变大的。还有一篇文章,详细解释了源代码,源代码可以在这里找到。

我想询问一下这个解释:

/* This over-allocates proportional to the list size, making room
 * for additional growth.  The over-allocation is mild, but is
 * enough to give linear-time amortized behavior over a long
 * sequence of appends() in the presence of a poorly-performing
 * system realloc().
 * Add padding to make the allocated size multiple of 4.
 * The growth pattern is:  0, 4, 8, 16, 24, 32, 40, 52, 64, 76, ...
 * Note: new_allocated won't overflow because the largest possible value
 *       is PY_SSIZE_T_MAX * (9 / 8) + 6 which always fits in a size_t.
 */
new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;
/* Do not overallocate if the new size is closer to overallocated size
 * than to the old size.
 */

特别是这个计算:

new_allocated = ((size_t)newsize + (newsize >> 3) + 6) & ~(size_t)3;

我对这个计算的理解是,新分配的内存将等于当前大小(触发扩展的大小)加上当前大小右移三位(相当于除以八)再加上6。然后,这个结果与3的补码进行“与”运算,以确保最后两位为零,这样这个值就能被4整除。

我用这段代码生成了我的列表并报告了大小:

a = [i for i in range(108)]
print(sys.getsizeof(a))  # 920 bytes

b = [i for i in range(109)]
print(sys.getsizeof(b))  # 1080 bytes

当元素达到109个时,触发了重新调整大小,此时新大小为928字节。

上面的计算应该是这样的:

二进制计算

1048字节小于报告的1060字节。

文档中提到,这个过程在处理小列表时可能效果不好,所以我尝试了一个更大的列表。我不会在这里重复二进制的部分。

a = [i for i in range(10640)]
print(sys.getsizeof(a))  # 85176 bytes

b = [i for i in range(10641)]
print(sys.getsizeof(b))  # 95864 bytes

[85184 + (86184 >> 3) + 6] = 95838字节

当应用“& ~3”后,这个值会降到95836字节。同样,这个值还是低于报告的95864字节。

为什么报告的重新调整大小会大于计算出的重新调整大小呢?

1 个回答

0

我觉得你犯的错误是把计算放在字节上。其实应该在元素上进行计算,然后再加上一个空列表对象的额外开销。

这里有一个简单的模拟,展示了 [i for i in range(109)] 的情况:

size = 0
used = 0
for _ in range(109):
    used += 1
    if used > size:
        size = (used + ((used >> 3) + 6)) & ~3

print(f"allocated array size: {size * 8}")
print(f"full list size: {size * 8 + sys.getsizeof([])}")
print(f"should be the same as {sys.getsizeof([i for i in range(109)])}")

分配的数组大小:1024

完整列表的大小:1080

应该和1080一样

这里提到的8是 sizeof(void*) 在C语言中的大小,在32位系统上这个值是4。

撰写回答