【问题标题】:CPython internal structuresCPython 内部结构
【发布时间】:2009-02-21 10:41:40
【问题描述】:

GAE 有各种限制,其中之一是最大可分配内存块的大小为 1Mb(现在增加了 10 倍,但这不会改变问题)。限制意味着不能在 list() 中放置超过一定数量的项目,因为 CPython 会尝试为元素指针分配连续的内存块。拥有庞大的 list() 可能被认为是不好的编程习惯,但即使程序本身没有创建庞大的结构,CPython 也会在幕后维护一些。

似乎 CPython 正在维护单个全局对象列表或其他东西。 IE。具有许多小对象的应用程序倾向于分配越来越大的单个内存块。

第一个想法是 gc,禁用它会稍微改变应用程序的行为,但仍然保留了一些结构。

遇到此问题的最简单的简短应用程序是:

a = b = []
number_of_lists = 8000000
for i in xrange(number_of_lists):
    b.append([])
    b = b[0]

谁能告诉我如何防止 CPython 在应用程序中有许多对象时分配巨大的内部结构?

【问题讨论】:

  • 真正的问题是什么。为什么内存不足很重要?您有什么具体问题似乎需要巨大的内存块?您可能可以首先解决该问题以不分配内存。您试图解决的真正问题是什么?

标签: python google-app-engine data-structures internals cpython


【解决方案1】:

在 32 位系统上,您创建的 8000000 个列表中的每一个都将为列表对象本身分配 20 个字节,再加上 16 个字节用于列表元素的向量。因此,您尝试分配至少 (20+16) * 8000000 = 20168000000 字节,大约 20 GB。这是最好的情况,如果系统 malloc 只分配所请求的内存。

我计算了列表对象的大小如下:

  • PyListObject 结构本身中的 2 个指针(请参阅 listobject.h
  • 1 个指针和一个Py_ssize_t 用于列表对象的PyObject_HEAD 部分(请参阅object.h
  • 一个Py_ssize_t 用于PyObject_VAR_HEAD(也在object.h 中)

列表元素的向量被稍微过度分配以避免在每次追加时都必须调整它的大小 - 请参阅listobject.c 中的 list_resize。大小为 0、4、8、16、25、35、46、58、72、88,...因此,您的单元素列表将为 4 个元素分配空间。

你的数据结构是一个有点病态的例子,付出了可变大小列表对象的代价而不使用它——你所有的列表只有一个元素。您可以通过使用元组而不是列表来避免 12 字节的过度分配,但为了进一步减少内存消耗,您将不得不使用使用更少对象的不同数据结构。很难更具体,因为我不知道您要完成什么。

【讨论】:

    【解决方案2】:

    我对你在问什么有点困惑。在该代码示例中,不应该对任何内容进行垃圾收集,因为您实际上并没有杀死任何引用。您在 a 中保存对顶级列表的引用,并且在其中添加嵌套列表(在每次迭代时保存在 b 中)。如果你删除了 'a =',那么你得到了未引用的对象。

    编辑:作为对第一部分的回应,是的,Python 拥有一个对象列表,因此它可以知道要剔除什么。这是整个问题吗?如果没有,请评论/编辑您的问题,我会尽力帮助填补空白。

    【讨论】:

    • 我已经提出了一些背景问题以供澄清。这有帮助吗?
    【解决方案3】:

    你想用这个来完成什么

    a = b = []
    

    b = b[0]
    

    声明?在 Python 中看到这样的语句当然很奇怪,因为它们不会做你可能天真期望的事情:在那个例子中,absame 列表的两个名称(想想C 中的指针)。如果你正在做很多这样的操作,很容易混淆垃圾收集器(和你自己!),因为你有很多奇怪的引用漂浮在周围,没有被正确清除。

    如果不知道为什么要执行它看起来正在执行的操作,就很难诊断出该代码的问题。当然,它暴露了一些解释器的怪异之处......但我猜你正在以一种奇怪的方式处理你的问题,而更 Pythonic 的方法可能会产生更好的结果。

    【讨论】:

    • 我认为这只是一个询问一些底层 GC 结构的示例,但我也无法弄清楚。
    • 是的,这只是一个奇怪的例子。 a = b = [] 很容易解开(对一个开始为空的列表的两个引用),但是 b = b[0] 让我想尝试思考 CPython 的内部结构。或许我该去睡觉了……
    • 示例程序是生成大量无法被垃圾回收的链接对象的最简单方法。程序执行后指向最外层包含的list(),每个列表包含单个元素,即另一个包含另一个单个元素的列表...
    • 它实际上只是使它嵌套列表(在原始列表中,保存在'a'中)。我也花了一秒钟的时间去摸索。
    • 代码没有错,除非 GAE 由于某些内部 CPython 结构跨越最大可分配内存块而杀死应用程序。我正试图弄清楚如何解决这个问题。
    【解决方案4】:

    为了让您知道,Python 有自己的分配器。您可以在配置步骤中使用 --without-pyalloc 禁用它。

    但是,最大的 arena 是 256KB,所以这应该不是问题。您还可以使用 --with-pydebug 在启用调试的情况下编译 Python。这将为您提供有关内存使用的更多信息。

    我怀疑您的预感,并且确信 oefe 的诊断是正确的。列表使用连续内存,因此如果您的列表对于系统领域来说太大了,那么您就不走运了。如果你真的很喜欢冒险,你可以重新实现 PyList 以使用多个块,但这将是很多工作,因为 Python 的各个位都需要连续的数据。

    【讨论】:

      猜你喜欢
      • 2010-10-09
      • 2012-12-12
      • 1970-01-01
      • 2019-01-08
      • 2021-07-12
      • 1970-01-01
      • 1970-01-01
      • 2020-07-02
      • 2013-03-19
      相关资源
      最近更新 更多