为什么 JVM 需要预热?

Why does the JVM require warmup?

我知道在 Java 虚拟机 (JVM) 中,可能需要预热,因为 Java 使用延迟加载过程加载 classes,因此您希望确保在开始主事务之前初始化对象。我是一名 C++ 开发人员,还没有处理过类似的需求。

然而,我无法理解的部分如下:

  1. 您应该预热代码的哪些部分?
  2. 即使我对代码的某些部分进行预热,它会保持多长时间(假设该术语仅表示您的 class 对象在内存中保留多长时间)?
  3. 如果我有每次收到事件时都需要创建的对象,这有什么帮助?

考虑一个示例应用程序,该应用程序预期通过套接字接收消息,并且事务可以是新订单、修改订单和取消订单或已确认的事务。

请注意,该应用程序是关于高频交易 (HFT) 的,因此性能极其重要。

都是关于 JIT 编译器的,它在 JVM 上用于在运行时优化字节码(因为 javac 不能使用高级或激进的优化技术,因为字节码的平台独立性)

  1. 您可以预热处理消息的代码。实际上,在大多数情况下,您不需要通过特殊的预热周期来处理它:只需让应用程序启动并处理一些第一条消息 - JVM 将尽力分析代码执行和进行优化 :) 使用假样本手动预热会产生更糟糕的结果

  2. 代码将在一段时间后进行优化,并且会一直优化到程序流中的某些事件会降低代码状态(之后 JIT 编译器将尝试优化代码再次 - 这个过程永远不会结束)

  3. 短期对象也是要优化的主题,但通常它应该有助于您的消息处理终身代码更有效率

很少需要热身。在进行例如性能测试时,它是相关的,以确保 JIT 预热时间不会影响结果。

在正常的生产代码中,您很少会看到用于预热的代码。 JIT 会在正常处理过程中预热,因此为此引入额外的代码几乎没有什么好处。在最坏的情况下,您可能会引入错误、花费额外的开发时间甚至损害性能。

除非您确定需要某种热身,否则请不要担心。您描述的示例应用程序当然不需要它。

预热是指一段代码 运行 的次数足以使 JVM 停止解释并编译为本机(至少是第一次)。通常这是你不想做的事情。原因是 JVM 收集有关它在代码生成期间使用的相关代码的统计信息(类似于配置文件引导优化)。因此,如果有问题的代码块 "warmed" 包含与真实数据具有不同属性的虚假数据,那么您很可能会损害性能。

编辑:由于 JVM 无法执行整个程序的静态分析(它无法知道应用程序将加载什么代码),因此它可以根据收集到的统计信息对类型进行一些猜测。例如,当在确切的调用位置调用虚函数(在 C++ 中)时,它确定所有类型都具有相同的实现,然后将调用提升为直接调用(甚至内联)。如果以后该假设被证明是错误的,那么旧代码必须 "uncompiled" 才能正常运行。 AFAIK HotSpot 将调用站点分类为单态(单一实现)、双态(正好是两个..转换为 if (imp1-type) {imp1} else {imp2} )和完全多态..虚拟调度。

还有另一种情况会发生重新编译......当您进行分层编译时。第一层将花费更少的时间来尝试生成好的代码,如果方法是 "hot-enough",那么更昂贵的编译时代码生成器就会启动。

Which parts of the code should you warmup?

这个问题一般没有答案。这完全取决于您的应用程序。

Even if I warmup some parts of the code, how long does it remain warm (assuming this term only means how long your class objects remain in-memory)?

只要您的程序对它们有引用,对象就会保留在内存中,没有任何特殊的弱引用使用或类似的东西。了解您的程序何时 "has a reference" 可能比您乍一看可能更晦涩,但它是 Java 中内存管理的基础,值得付出努力。

How does it help if I have objects which need to be created each time I receive an event.

这完全取决于应用程序。一般没有答案。

我鼓励您学习和使用 Java 来了解 class 加载、内存管理和性能监控等内容。实例化一个对象需要一些时间,通常加载 class 需要更多时间(当然,通常这样做的次数要少得多)。 通常,一旦 class 被加载,它会在程序的整个生命周期内保留在内存中——这是您应该理解的事情,不只是得到答案。

如果您还不知道,也可以学习一些技巧。一些程序使用 "pools" 个对象,在实际需要它们之前实例化,然后在需要时移交处理。这允许程序的时间关键部分避免在时间关键期间实例化所花费的时间。池维护一个对象集合(10?100?1000?10000?),并在需要时实例化更多对象等。但是管理池是一项重要的编程工作,当然,您会占用池中对象的内存.

完全有可能耗尽足够的内存来更频繁地触发垃圾收集,并减慢您打算加速的系统。这就是为什么您需要了解它的工作原理,而不仅仅是 "get an answer".

另一个考虑因素——到目前为止,为使程序更快而付出的大部分努力都被浪费了,因为没有必要。如果没有对正在考虑的应用程序的广泛经验,and/or 系统测量,您根本不知道在哪里( 或是否 )优化甚至会被注意到。 System/program 避免病态缓慢情况的设计很有用,而且不会花费 'optimization' 的时间和精力。大多数时候,这就是我们每个人所需要的。

-- 编辑 -- 将即时编译添加到要研究和理解的事物列表中。

Why JVM requires warmup?

现代 (J)VM 在 运行 时间收集统计信息,了解最常使用的代码及其使用方式。一个(数百甚至数千个)示例是对虚函数(用 C++ 术语)的调用的优化,它只具有实现。根据其定义,这些统计数据只能在 运行 时间收集。

Class 加载本身也是预热的一部分,但它显然会在那些 类 中的代码执行之前自动发生,因此无需担心[=15] =]

Which parts of the code should you warmup?

对应用程序性能至关重要的部分。重要的部分是 "warm it up" 与正常使用时使用的方式相同,否则将进行错误的优化(并在以后撤消)。

Even if I warmup some parts of the code, how long does it remain warm (assuming this term only means how long your class objects remain in-memory)?

这真的很难说,基本上 JIT 编译器会不断监视执行和性能。如果达到某个阈值,它将尝试优化事物。然后它将继续监视性能以验证优化是否确实有帮助。如果不是,它可能会取消优化代码。也可能会发生使优化无效的事情,例如加载新的 类。我认为这些事情是不可预测的,至少不是基于 Whosebug 的答案,但是有一些工具可以告诉你 JIT 在做什么:https://github.com/AdoptOpenJDK/jitwatch

How does it help if I have objects which need to be created each time I receive an event.

一个简单的例子可能是:你在一个方法中创建对象,因为引用离开了方法的范围,这些对象将被存储在堆上,并最终被垃圾收集器收集。如果使用这些对象的代码被大量使用,它可能最终会被内联到一个单一的大方法中,可能会重新排序以致无法识别,直到这些对象只存在于这个方法中。那时它们可以被放入堆栈并在方法退出时被移除。这可以节省大量的垃圾收集,并且只会在一些预热后发生。

综上所述:我对人们需要做任何特别的热身活动这一观点持怀疑态度。只需启动您的应用程序,然后使用它,JIT 编译器就会做的很好。如果您遇到问题,请了解 JIT 对您的应用程序做了什么以及如何微调该行为或如何编写您的应用程序以使其受益最大。

我真正知道需要热身的唯一情况是基准测试。因为如果你在那里忽略它,你几乎可以保证得到虚假结果。

Which parts of the code should you warm up?

通常,您无需执行任何操作。但是对于低延迟应用程序,您应该预热系统中的关键路径。你应该进行单元测试,所以我建议你 运行 那些在启动时预热代码的人。

即使您的代码已经预热,您也必须确保您的 CPU 缓存保持温暖。在阻塞操作之后,您会看到性能显着下降,例如网络 IO,最多 50 微秒。通常这不是问题,但如果您大部分时间都试图保持在 50 微秒以下,那么大多数时候这将是一个问题。

注意:预热可以让逃逸分析开始并将一些对象放在堆栈上。这意味着不需要优化此类对象。最好在优化代码之前对您的应用程序进行内存分析。

Even if I warm up some parts of the code, how long does it remain warm (assuming this term only means how long your class objects remain in-memory)?

没有时间限制。这取决于 JIt 是否检测到它在优化代码时所做的假设是否被证明是不正确的。

How does it help if I have objects which need to be created each time I receive an event?

如果您想要低延迟或高性能,您应该创建尽可能少的对象。我的目标是生产少于 300 KB/sec。使用此分配率,您可以拥有一个 Eden space 大到足以每天进行一次次要收集。

Consider for an example an application that is expected to receive messages over a socket and the transactions could be New Order, Modify Order and Cancel Order or transaction confirmed.

我建议您尽可能重复使用对象,但如果它在您的分配预算之内,则可能不值得担心。

Note that the application is about High Frequency Trading (HFT) so performance is of extreme importance.

您可能对我们用于不同投资银行和对冲基金的 HFT 系统的开源软件感兴趣。

http://chronicle.software/

My production application is used for High frequency trading and every bit of latency can be an issue. It is kind of clear that at startup if you don't warmup your application, it will lead to high latency of few millis.

您可能会对 https://github.com/OpenHFT/Java-Thread-Affinity 特别感兴趣,因为此库有助于减少关键线程中的调度抖动。

And also it is said that the critical sections of code which requires warmup should be ran (with fake messages) atleast 12K times for it to work in an optimized manner. Why and how does it work?

代码是使用后台线程编译的。这意味着即使一个方法可能有资格编译为本机代码,但这并不意味着它已经这样做了,尤其是在启动时编译器已经很忙的时候。 12K不是不合理,但可以更高

我总是这样想象它:

作为(C++ 开发人员),您可以想象一个 自动 迭代方法,由 jvm compiling/hotloading/replacing 各种位组成(想象中的模拟) gcc -O0,-O1,-O2,-O3 变体(如果认为有必要,有时会还原它们)

我敢肯定这不是严格意义上发生的事情,但对于 C++ 开发人员来说可能是一个有用的类比。

在标准 jvm 上,jit 考虑片段所需的时间由 -XX:CompileThreshold 设置,默认情况下为 1500。 (来源和 jvm 版本各不相同 - 但我认为那是 jvm8)

我手边的 book 在 Host Performace JIT Chapter (p59) 中指出以下优化是在 JIT 期间完成的:

  • 内联
  • 锁消除
  • 虚拟呼叫消除
  • 非易失性内存写入消除
  • 本机代码生成

编辑:

关于评论

I think 1500 may be just enough to hint to JIT that it should compile the code into native and stop interpreting. would you agree?

我不知道这是否只是一个提示,但由于 openjdk 是开源的,让我们看看 globals.hpp#l3559@ver-a801bc33b08c(对于 jdk8u)

中的各种限制和数字

(我不是 jvm 开发人员,这可能是完全错误的地方)

Compiling a code into native does not necessarily mean it is also optimized.

据我了解 - 是的;特别是如果你的意思是 -Xcomp (强制编译) - 这个 blog 甚至声明它阻止 jvm 进行任何分析 - 因此优化 - 如果你不 运行 -Xmixed (默认值)。

So a timer kicks in to sample frequently accessed native code and optimize the same. Do you know how we can control this timer interval?

我真的不知道细节,但我链接的 gobals.hpp 确实定义了一些频率间隔。