pypy3

概要

pypy3 [选项] [-c cmd|-m mod|file.py|-] [arg…]

选项

-i 在运行脚本后以交互方式检查。
-O 跳过断言语句。
-OO 除了 -O 之外,在导入模块时删除文档字符串。
-c CMD 作为 CMD 传递的程序(终止选项列表)。
-S 在初始化时不要 import site
-s 不要将用户站点目录添加到 sys.path
-u 无缓冲的二进制 stdoutstderr
-h, --help 显示帮助消息并退出。
-m MOD 要作为脚本运行的库模块(终止选项列表)。
-W ARG 警告控制(argaction:message:category:module:lineno)。
-E 忽略环境变量(例如 PYTHONPATH)。
-B 禁用写入字节码 (.pyc) 文件。
-X track-resources
 每当垃圾收集器关闭文件或套接字时,都会产生一个 ResourceWarning
--version 打印 PyPy 版本。
--info 打印有关此 PyPy 可执行文件的翻译信息。
--jit ARG 低级 JIT 参数。主要是内部的。运行 --jit help 以获取更多信息。

环境

PYTHONPATH
将目录添加到 pypy3 的模块搜索路径。格式与 shell 的 PATH 相同。
PYTHONSTARTUP
此变量引用的脚本将在交互模式下显示第一个提示之前执行。
PYTHONDONTWRITEBYTECODE
如果设置为非空值,则等效于 -B 选项。禁用写入 .pyc 文件。
PYTHONINSPECT
如果设置为非空值,则等效于 -i 选项。在运行指定的脚本后以交互方式检查。
PYTHONIOENCODING
如果设置了此值,它将覆盖用于 stdin/stdout/stderr 的编码。语法是 encodingname:errorhandler errorhandler 部分是可选的,与 str.encode 中的含义相同。
PYTHONNOUSERSITE
如果设置为非空值,等效于 -s 选项。不要将用户站点目录添加到 sys.path 中。
PYTHONWARNINGS
如果设置,等效于 -W 选项(警告控制)。该值应为 -W 参数的逗号分隔列表。
PYPYLOG

如果设置为非空值,则启用日志记录,格式为

fname+fname
用于分析的日志记录:包括所有 debug_start/debug_stop,但不包括任何嵌套的 debug_printfname 可以是 - 以将日志记录到 stderr。如果 fname 中包含 :,则可以使用 +fname 形式。
:fname
完整日志记录,包括 debug_print
prefix:fname
条件日志记录。可以指定多个前缀,用逗号分隔。只有名称与前缀匹配的部分才会被记录。

PYPYLOG=jit-log-opt,jit-backend:logfile 将生成适合 jitviewer 的日志,jitviewer 是用于调试 PyPy 中性能问题的工具。

PYPY_IRC_TOPIC
如果设置为非空值,则在交互模式启动时打印一个随机的 #pypy IRC 主题。

PyPy 的默认垃圾收集器称为 incminimark - 它是一个增量式、分代移动收集器。在这里,我们希望解释一下它的工作原理以及如何调整它以适应工作负载。

Incminimark 首先在所谓的 nursery 中分配对象 - 这是年轻对象的存放地,分配非常便宜,只需一个指针递增即可。nursery 的大小是一个非常重要的变量 - 具体取决于你的工作负载(一个或多个进程)和缓存大小,你可能需要通过 PYPY_GC_NURSERY 环境变量进行实验。当 nursery 满了时,就会执行一次次要收集。释放的对象不再可引用,并且只是因为不再被引用而死亡;另一方面,发现仍然存活的对象必须存活下来,并且会被从 nursery 复制到旧代。要么复制到 arenas,arenas 是相同大小对象的集合,要么直接使用 malloc 分配,如果它们更大。(第三类,即非常大的对象,最初是在 nursery 之外分配的,并且永远不会移动。)

由于 Incminimark 是一个增量式 GC,因此主要收集是增量式的:目标是避免任何暂停时间超过 1 毫秒,但在实践中,它取决于堆的大小和特征:偶尔,可能会出现 10-100 毫秒之间的暂停。

半手动 GC 管理

如果程序的某些部分需要低延迟,你可能需要精确控制 GC 何时运行,以避免意外暂停。请注意,这仅对主要收集有效,而次要收集将照常工作。

如上所述,完整的 major 收集包括 N 个步骤,其中 N 取决于堆的大小;一般来说,无法预测完成收集需要多少个步骤。

gc.enable()gc.disable() 控制 GC 是否自动运行收集步骤。当 GC 被禁用时,内存使用量将无限增长,除非你手动调用 gc.collect()gc.collect_step()

gc.collect() 运行完整的 major 收集。

gc.collect_step() 运行单个收集步骤。它返回一个 GcCollectStepStats 类型的对象,与传递给相应 GC Hooks 的对象相同。以下代码大致等同于 gc.collect()

while True:
    if gc.collect_step().major_is_done:
        break

有关此 API 用法的实际示例,您可以查看第三方模块 pypytools.gc.custom,它还提供了一个 with customgc.nogc() 上下文管理器来标记禁止 GC 的部分。

碎片化

在我们讨论“碎片化”问题之前,我们需要一些精确度。有两种相关但不同的问题

  • 如果程序分配了大量内存,然后通过删除对它的所有引用来释放所有内存,那么我们可能会期望看到 RSS 下降。(RSS = Linux 上的驻留集大小,如“top”所示;它是从操作系统角度来看的实际内存使用量的近似值。)这可能不会发生:RSS 可能会保持在最高值。这个问题更准确地说是由进程没有将“空闲”内存返回给操作系统引起的。我们将这种情况称为“未返回内存”。
  • 在完成上述操作后,如果 RSS 没有下降,那么至少将来的分配不应该导致 RSS 进一步增长。也就是说,只要进程还有剩余的未返回内存,它应该重用未返回内存。如果这种情况没有发生,RSS 会变得更大,我们就会遇到真正的碎片化问题。

gc.get_stats

gc 模块中有一个特殊的函数,名为 get_stats(memory_pressure=False)

memory_pressure 控制是否报告来自 GC 外部分配的对象的内存压力,这需要遍历整个堆,因此默认情况下它被禁用,因为它会造成成本。在调试神秘的内存消失时启用它。

示例调用如下所示

>>> gc.get_stats(True)
Total memory consumed:
GC used:            4.2MB (peak: 4.2MB)
   in arenas:            763.7kB
   rawmalloced:          383.1kB
   nursery:              3.1MB
raw assembler used: 0.0kB
memory pressure:    0.0kB
-----------------------------
Total:              4.2MB

Total memory allocated:
GC allocated:            4.5MB (peak: 4.5MB)
   in arenas:            763.7kB
   rawmalloced:          383.1kB
   nursery:              3.1MB
raw assembler allocated: 0.0kB
memory pressure:    0.0kB
-----------------------------
Total:                   4.5MB

在这种特殊情况下,它只是在启动时,GC 消耗的内存相对较少,并且未使用的已分配内存更少。如果存在大量未返回内存或实际碎片化,“已分配”可能远高于“已使用”。一般来说,“峰值”将更接近 RSS 报告的实际内存消耗。事实上,将内存返回给操作系统是一个难题,尚未解决。在 PyPy 中,只有当一个 arena 完全空闲时才会发生这种情况——一个连续的 64 页块,每块 4 或 8 KB。对于“rawmalloced”类别来说,这种情况也很少见,至少对于 malloc() 的常见系统实现来说是如此。

各个字段的详细信息

  • GC in arenas - 保存在 arenas 中的小型旧对象。如果“已分配”的数量远高于“已使用”的数量,那么我们就有未返回的内存。这里可能存在内部碎片化,但可能性很小。但是,这种未返回的内存不能被任何 malloc() 重用,包括来自“rawmalloced”部分的内存。
  • GC rawmalloced - 使用 malloc 分配的大型对象。这提供了使用 malloc() 分配的当前(第一块文本)和峰值(第二块文本)内存。无法轻松报告由 malloc() 引起的未返回内存或碎片化。通常,如果 RSS 远大于“GC 已分配”报告的总内存,您可以猜测存在一些内存,但请记住,该总数不包括 PyPy 的 GC 完全不知道的 malloc 的内存。如果您猜测存在一些内存,请考虑使用 jemalloc 而不是系统 malloc。
  • nursery - 为 nursery 分配的内存量,在启动时固定,通过环境变量控制
  • raw assembler allocated - JIT 认为自己负责的汇编器内存量
  • memory pressure, if asked for - 我们认为通过外部 malloc 分配的内存量(例如在 SSL 上下文中加载证书存储),它由 GC 对象保持活动状态,但未在 GC 中计算

GC 钩子

GC 钩子是用户定义的函数,在特定 GC 事件发生时被调用,可用于监控 GC 活动和暂停。您可以通过设置以下属性来安装钩子

gc.hooks.on_gc_minor
每次发生次要收集时调用。它对应于 PYPYLOG 内部的 gc-minor 部分。
gc.hooks.on_gc_collect_step
每次发生主要收集的增量步骤时调用。它对应于 PYPYLOG 内部的 gc-collect-step 部分。
gc.hooks.on_gc_collect
在最后一个增量步骤之后调用,当主要收集完全完成时。它对应于 PYPYLOG 内部的 gc-collect-done 部分。

要卸载钩子,只需将相应的属性设置为 None。要一次安装所有钩子,您可以调用 gc.hooks.set(obj),它将在 obj 上查找方法 on_gc_*。要一次卸载所有钩子,您可以调用 gc.hooks.reset()

钩子调用的函数接收一个 stats 参数,其中包含有关事件的各种统计信息。

请注意,PyPy 无法在 GC 事件之后立即调用钩子,而必须等到解释器处于已知状态并且调用用户定义的代码无害时才能调用。可能会发生在调用钩子之前发生多个事件:在这种情况下,您可以检查值 stats.count 以了解自上次调用钩子以来事件发生了多少次。类似地,stats.duration 包含自上次调用钩子以来 GC 为此特定事件花费的**总**时间。

另一方面,stats 对象的所有其他字段仅与该系列的**最后一个**事件相关。

on_gc_minor 钩子中,GcMinorStats 的属性为

count
自上次调用钩子以来发生的次要收集次数。
duration
自上次调用钩子以来,在次要收集中花费的总时间(以秒为单位)。
duration_min
自上次调用钩子以来,最快的次要收集的持续时间。
duration_max
自上次调用钩子以来,最慢的次要收集的持续时间。
total_memory_used
次要收集结束时使用的内存量(以字节为单位)。这包括竞技场中使用的内存(用于 GC 管理的内存)和 raw-malloced 内存(例如,numpy 数组的内容)。
pinned_objects
固定对象的数量。

on_gc_collect_step 钩子中,GcCollectStepStats 的属性为

countdurationduration_minduration_max
见上文。
oldstatenewstate
指示 GC 在步骤之前和之后的狀態的整數。
major_is_done
布尔值,指示这是否是主要收集的最后一步

变量 oldstatenewstate 的值是以下常量之一,定义在 gc.GcCollectStepStats 中:STATE_SCANNINGSTATE_MARKINGSTATE_SWEEPINGSTATE_FINALIZINGSTATE_USERDEL。可以通过索引 GC_STATES 元组来获取其字符串表示。

on_gc_collect 钩子中,GcCollectStats 的属性如下:

count
见上文。
num_major_collects
自程序启动以来执行的主要收集次数。与 count 不同,这是一个始终增长的计数器,不会在调用之间重置。
arenas_count_beforearenas_count_after
主要收集前后使用的区域数量。
arenas_bytes
GC 管理的对象使用的总字节数。
rawmalloc_bytes_beforerawmalloc_bytes_after
主要收集前后,原始分配的对象使用的总字节数。
pinned_objects
固定对象的数量。

请注意,GcCollectStats **没有** duration 字段。这是因为所有 GC 工作都在 gc-collect-step 中完成:gc-collect-done 仅用于提供额外的统计信息,但不会执行任何实际工作。

以下是一个使用 GC 钩子的示例

import sys
import gc

class MyHooks(object):
    done = False

    def on_gc_minor(self, stats):
        print 'gc-minor:        count = %02d, duration = %d' % (stats.count,
                                                                stats.duration)

    def on_gc_collect_step(self, stats):
        old = gc.GcCollectStepStats.GC_STATES[stats.oldstate]
        new = gc.GcCollectStepStats.GC_STATES[stats.newstate]
        print 'gc-collect-step: %s --> %s' % (old, new)
        print '                 count = %02d, duration = %d' % (stats.count,
                                                                stats.duration)

    def on_gc_collect(self, stats):
        print 'gc-collect-done: count = %02d' % stats.count
        self.done = True

hooks = MyHooks()
gc.hooks.set(hooks)

# simulate some GC activity
lst = []
while not hooks.done:
    lst = [lst, 1, 2, 3]

环境变量

PyPy 的默认 incminimark 垃圾收集器可以通过多个环境变量进行配置

PYPY_GC_NURSERY
育苗器大小。默认为最后一级缓存的 1/2,如果未知则为 4M,如果最后一级缓存太小则为 4M。小值(如 1 或 1KB)对于调试很有用。
PYPY_GC_NURSERY_DEBUG
如果设置为非零值,将用垃圾填充育苗器,以帮助调试。
PYPY_GC_INCREMENT_STEP
标记步骤期间标记的内存大小。默认为育苗器大小的 2 倍。如果设置过高,您的 GC 根本就不是增量的。最小值设置为幸存者次要收集大小的 1.5 倍,因此我们始终回收任何东西。
PYPY_GC_MAJOR_COLLECT
主要收集内存因子。默认为 1.82,这意味着当消耗的内存等于上一次主要收集结束后实际使用的内存的 1.82 倍时,触发主要收集。
PYPY_GC_GROWTH
主要收集阈值的最高增长率。默认为 1.4。在内存突然增长时,例如当内存使用量出现暂时峰值时,有助于比平时更频繁地收集。
PYPY_GC_MAX
最大堆大小。如果接近此限制,它将首先更频繁地收集,然后引发 RPython MemoryError,如果这还不够,则会用致命错误使程序崩溃。尝试 1.6GB 之类的值。
PYPY_GC_MAX_DELTA
主要收集阈值永远不会设置为收集后实际使用的量的 PYPY_GC_MAX_DELTA 以上。默认为总 RAM 大小的 1/8(在 32 位系统上,此值限制为最多 2/3/4GB)。尝试 200MB 之类的值。
PYPY_GC_MIN
当内存大小低于此限制时,不要收集。这有助于避免在非常小的程序中花费所有时间进行 GC。默认为育苗器的 8 倍。
PYPY_GC_DEBUG
启用围绕收集的额外检查,这些检查对于正常使用来说太慢了。值为 0(关闭)、1(在主要收集时)或 2(也在次要收集时)。
PYPY_GC_MAX_PINNED
任何时间点上固定对象的最大数量。默认情况下,该值取决于托儿所的大小和托儿所内最大对象的大小,是一个保守的值。通过将其设置为 0,可以用于调试。

另请参阅

python3(1)