为什么 JIT 编译的代码消耗的内存比编译或解释的代码要多得多?

7

编译型语言如C消耗的内存较少。

解释型语言如Python消耗的内存较多,这是可以理解的。

使用JIT技术,程序在运行时会被(有选择地)编译成机器代码。那么,JIT编译后的程序的内存消耗不应该介于编译型和解释型之间吗?

然而,JIT编译的程序(如PyPy)比等效的解释型程序(如Python)消耗的内存要多几倍。为什么呢?


所有人都说 JIT 是 Pypy 中增加内存使用的原因,但这不是全部的故事。尽管 Pypy 的某些内存结构更紧凑(例如所有整数的列表),但 Pypy 有各种垃圾回收器可与之一起使用,它们肯定会影响内存使用量。当前 Pypy 的默认垃圾回收器不进行引用计数以实现加速。由于这个原因,对象可以停留在内存中的时间比在 CPython 中更长,从而程序在 Pypy 中可能会占用更多的内存空间。 - Justin Peel
2个回答

8
跟踪JIT编译器需要更多的内存,因为它们不仅需要保存虚拟机的字节码,还需要保存可直接执行的机器代码。但这只是故事的一半。
大多数JIT还会保存有关字节码(甚至机器代码)的许多元数据,以便确定哪些需要JIT,哪些可以保持不变。跟踪JIT(例如LuaJIT)还会创建跟踪快照,用于在运行时微调代码,执行诸如循环展开或分支重排序之类的操作。
有些JIT还会保留常用代码段的缓存或快速查找缓冲区,以加速JIT代码的创建(LuaJIT通过DynAsm实现此目的,如果正确使用,则实际上有助于减少内存使用)。
内存使用情况极大地取决于所采用的JIT引擎及其编译的语言的性质(强类型与弱类型)。一些JIT采用先进的技术,如基于SSA的寄存器分配器和变量存活性分析,这些优化技术也有助于消耗内存,以及像循环变量提升之类的常见技术。

1
此外,JIT 是实时编译,因此必须在优化和速度之间进行权衡。离线编译器可以花费5秒钟来优化一个函数,但JIT则不行。 - Raymond Chen
@RaymondChen:完全取决于JIT,如果它编写正确,您不需要太多的优化折衷,只有真正放弃昂贵的分析技术。 - Necrolis
此外,JIT通常是专门化编译器。 JIT编译的整个意义在于编译器可以等待并查看运行时使用的内容,并为其进行特定优化。这意味着(取决于JIT),在内存中给定字节码部分可能会有几个不同版本的机器代码,以及原始字节码,以防出现任何不适合已编译版本的新情况。 - Ben
添加JIT本身的大小。一些(如Mono)相对较小,但有些(如HotSpot)非常庞大和笨重。 - SK-logic

5
请注意你所谈论的内存使用类型。
编译为C的代码相对使用较少的内存来存储编译后的机器码本身。
我期望给定算法的Python字节码实际上比类似算法的编译后C代码要小,因为Python字节码操作更高级,所以通常需要较少的操作来完成一项任务。但是,Python程序也会在内存中有Python解释器的编译代码,这本身就是一个相当大且复杂的程序。此外,典型的Python程序将比典型的C程序有更多的标准库在内存中(如果静态链接,则C程序可以剥离它实际上不使用的所有函数;如果动态链接,则它与任何其他使用它的进程共享编译的代码)。
然后,PyPy还有JIT编译器的机器码,以及从Python字节码生成的机器码(它不会消失,也必须保留下来)。因此,您的直觉(即JIT系统“应该”在编译语言和完全解释语言之间消耗内存)是不正确的。
但是,在所有这些之上,您还需要考虑程序操作的数据结构实际使用的内存。这种差异极大,并且与程序是预先编译、解释还是解释并JIT无关。一些编译器优化将减少内存使用(无论是预先应用还是即时应用),但许多实际上会通过交换内存使用来获得速度。对于操作任何大量数据的程序,它将完全超过代码本身使用的内存。
当您说:
“相反,JIT程序(如PyPy)消耗的内存比等效的解释程序(如Python)多数倍。为什么?”
您在考虑哪些程序?如果您实际进行了任何比较,我猜测从您的问题中可以看出它们将是PyPy和CPython之间的比较。我知道PyPy的许多数据结构实际上比CPython更小,但这与JIT无关。
如果程序的主要内存使用是代码本身,则JIT编译器会增加巨大的内存开销(用于编译器本身和已编译的代码),并且不能通过优化来“赢回”内存使用。如果程序数据结构的主要内存使用,则我不会惊讶地发现PyPy使用的内存比CPython少得多,无论JIT是否启用。
你的“为什么?”没有一个简单的答案,因为你问题中的语句并不直接正确。哪个系统使用更多内存取决于许多因素;JIT编译器的存在或不存在是一个因素,但它并不总是重要的。

我不同意你的观点:“如果程序的主要内存使用是代码本身,那么JIT编译器会增加巨大的内存开销......” - 对于一个设计良好的热点JIT编译器来说,这是错误的。我也不同意你的观点:“如果主要内存使用是程序数据结构,那么[...] PyPy比CPython使用的内存少得多” - PyPy并没有那么好。 - user811773
@Atom 一种JIT编译器在运行时将代码转换为代码。一个特化的JIT编译器必须保留原始代码(非特化的JIT编译器并不是非常有趣,因为它可以作为预编译器实现,获得不必在运行时运行它的效率,并且能够花更多时间进行优化)。因此,它必须添加至少O(n)的空间开销,其中n是由JIT编译器编译的代码量。再加上JIT本身的内存... - Ben
@Atom 确实通常情况下 JIT 不会编译程序的大部分代码,所以我可能有点夸张了。但是一个程序中有多少代码被 JIT 编译始终取决于该程序。当然,对于任何给定的 JIT 系统,都存在一些程序与在非 JIT 环境下执行相比,将付出相当数量的内存开销的情况。 - Ben
@Atom关于PyPy的内存使用:PyPy对类实例的实现使用的内存使用量与在CPython中到处使用__slots__相当。请参见http://morepypy.blogspot.com.au/2010/11/efficiently-implementing-python-objects.html。在某些情况下,这是非常显着的内存节省。他们还有其他的空间优化。我不知道整个真实程序的总体内存使用情况如何与CPython相比,但至少对于图片的某些部分,PyPy是“那么好”。因此,“我不会感到惊讶……”,而不是“PyPy绝对使用更少的内存”。 - Ben

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接