【问题标题】:CPython List Implementation SpecificsPython 列表实现细节
【发布时间】:2021-12-12 01:08:32
【问题描述】:

我知道这里回答了一个类似的问题:How is Python's List Implemented? 但我想询问更多关于细节的信息。我想了解更多关于 CPython 如何实现列表大小调整的信息。我对C不太熟悉,所以看源代码有困难。

我想我理解的是列表Py_ssize_t ob_size的大小和分配给列表Py_ssize_t allocated的数量,当ob_size达到allocated时,需要分配更多的内存。我假设如果系统允许,内存将被分配到位,否则列表将被复制到内存中的另一个位置。特别是,我问的是选择将allocated 更改多少。从listobject.c,新分配的内存如下:

new_allocated = (size_t)newsize + (newsize >> 3) + (newsize < 9 ? 3 : 6);

基本上,我们使分配的对象大小比所需的对象大小多 1/8(忽略常量)。我想知道为什么选择这个 1/8?在我的入门编码课程中,我记得学习过 ArrayLists,它在满时会翻倍。也许也可以选择增加 1/2 或 1/4。 增加越小,从一长串追加中分摊的时间就越差(仍然是常数,但系数更大),所以 1/8 似乎是一个糟糕的选择。我的猜测是每次分配少量将增加能够重新分配到位的机会。这是正确的推理吗?这个 CPython 实现在实践中运行良好吗?

注意:在删除元素后减少分配的列表内存时,当列表下降到原始大小的一半时会发生这种情况,从这部分代码可以看出:

/* 当先前的过度分配大到足以容纳 newsize 时,绕过 realloc()。如果 newsize 低于分配大小的一半,则继续执行 realloc() 以缩小列表。 */

if (allocated >= newsize && newsize >= (allocated >> 1)) {

【问题讨论】:

  • 从 Big-O 的角度来看,每次加倍分配可能是最佳的 - 但在现实世界中,这可能会浪费字面 GB 的内存。
  • 这是一个有趣的,最近关于该策略的讨论:bugs.python.org/issue38373
  • 那次讨论中的一句话:“我们绝对应该重新审视过度分配策略。我上一次研究现有策略是在 2004 年。从那时起,速度/空间权衡考虑的权重变了。”

标签: python list cpython python-internals


【解决方案1】:

嗯,基于the 21-year-old commit that implemented that behavior为什么是“因为它改善了 Tim Peters 的 Win98 机器上的内存行为”。从下面的提交中复制 Tim 的 cmets。

在我的 Win98SE 机器上不可能精确计时,但这显然是 对于合理的 list.append() 情况,即使在此框中也更快。我给 这不是因为调整大小的策略,而是因为摆脱了整数 计算时的乘法和除法(有利于移位) 向上取整。

对于不合理的 list.append() 情况,Win98SE 现在显示线性行为 for one-at-time 追加到一个包含大约 3500 万个元素的列表。然后 由于致命的 地址空间碎片,它会因 MemoryError 而死 (有很多可用的 VM,但此时 Win9X 已经破坏了用户 空间分成许多不同的堆,其中没有一个有足够的连续空间 留下来调整列表的大小,无论出于何种原因,Win9x 都没有合并 死堆)。在补丁之前,它得到了相同的 MemoryError 原因,但是一旦列表达到了大约 200 万个元素。

尚未尝试过Win2K但寄予厚望的极端list.append() 现在会表现得更好(NT和Win2K没有碎片地址空间, 但在列表变大之前遭受了明显的二次时间行为)。

对于其他系统,我依赖常识:替换整数 * 和 / > 不能合理地伤害,函数调用的数量没有 更改了,并且相当小的列表的总操作数约为 一样(虽然现在操作更便宜)。

...

这会与列表大小成比例地过度分配,从而腾出空间 以获得额外的增长。过度分配是温和的,但 足以在很长一段时间内提供线性时间摊销行为 在表现不佳的情况下执行 appends() 序列 系统 realloc() (这是一个现实,例如,跨越所有口味 Windows,Win9x 的行为特别糟糕——和 我们在 Win9x 上仍然存在地址空间碎片问题 即使使用这个方案,虽然它需要更长的列表来 比以前更能激怒他们)。

Raymond Hettinger 在this commit 中进一步调整了这些值:

Py2.3 方法过度分配了最多 8 个元素的小列表。 最后一次签入会将其限制为一个,但速度会减慢(20% 到 30%) 创建 3 到 8 个元素之间的小列表。

此调整平衡了两者,将过度分配限制为 3 个元素 (从 Py2.3 显着减少空间消耗)并且运行速度更快 比上次签到。

增长模式的第一部分(0、4、8、16)与 仅在超过 2 的幂时触发数据移动的分配器 边界。此外,偶数与常见的数据对齐方式非常吻合。

【讨论】:

    猜你喜欢
    • 2016-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多