52

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

但是,我无法理解的部分如下:

  1. 您应该预热代码的哪些部分?
  2. 即使我预热了代码的某些部分,它还能保持多久(假设这个术语只意味着你的类对象在内存中保持多长时间)?
  3. 如果我有需要在每次收到事件时创建的对象,它有什么帮助?

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

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

4

7 回答 7

35

您应该预热代码的哪些部分?

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

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

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

即使我预热了代码的某些部分,它还能保持多久(假设这个术语只意味着你的类对象在内存中保持多长时间)?

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

如果我有需要在每次收到事件时创建的对象,它有什么帮助?

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

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

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

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

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

http://chronicle.software/

我的生产应用程序用于高频交易,每一点延迟都可能是一个问题。很明显,在启动时如果你不预热你的应用程序,它会导致几毫秒的高延迟。

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

并且据说需要预热的代码的关键部分应该运行(使用假消息)至少 12K 次,以便它以优化的方式工作。为什么以及如何工作?

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

于 2016-03-24T17:20:19.170 回答
21

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

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

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

于 2016-03-24T11:00:06.333 回答
11

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

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

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

于 2016-03-24T10:57:38.600 回答
3

为什么JVM需要预热?

现代 (J)VM 在运行时收集有关哪些代码最常使用以及如何使用的统计信息。一个(如果不是数千个)示例是对仅在实现上的虚拟函数(用 C++ 术语)的调用的优化。根据他们的定义,这些统计信息只能在运行时收集。

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

您应该预热代码的哪些部分?

对应用程序性能至关重要的部分。重要的部分是“热身”,就像在正常使用期间使用的一样,否则将完成错误的优化(并在以后撤消)。

即使我预热了代码的某些部分,它还能保持多久(假设这个术语只意味着你的类对象在内存中保持多长时间)?

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

如果我有每次收到事件时都需要创建的对象,这有什么帮助。

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

尽管如此:我对一个人需要做任何特别的热身的想法持怀疑态度。只需启动您的应用程序并使用它,JIT 编译器就会很好地完成它。如果您遇到问题,请了解 JIT 对您的应用程序的作用以及如何微调该行为或如何编写您的应用程序以使其受益最多。

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

于 2016-03-24T11:09:25.320 回答
2

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

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

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

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

于 2016-03-24T10:49:51.030 回答
2

您应该预热代码的哪些部分?

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

即使我预热了代码的某些部分,它还能保持多久(假设这个术语只意味着你的类对象在内存中保持多长时间)?

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

如果我有每次收到事件时都需要创建的对象,这有什么帮助。

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

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

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

完全有可能用完足够的内存来更频繁地触发垃圾收集,并减慢您想要加速的系统。这就是为什么您需要了解它是如何工作的,而不仅仅是“得到答案”。

另一个考虑因素——到目前为止,大部分用于加快程序速度的努力都被浪费了,因为不需要。如果没有对所考虑的应用程序和/或系统测量的丰富经验,您根本不知道哪里(或是否)优化会很明显。避免缓慢的病理情况的系统/程序设计是有用的,并且几乎不需要“优化”的时间和精力。大多数时候,它是我们任何人都需要的。

-- 编辑 -- 将即时编译添加到要学习和理解的内容列表中。

于 2016-03-24T11:06:41.257 回答
1

我总是把它想象成这样:

您作为(C++ 开发人员)可以想象一种自动迭代方法,通过jvm编译/热加载/替换各种位(虚构的类似物)、、、、gcc -O0变体(如果认为有必要,有时会恢复它们-O1-O2-O3

我确信这并不是严格意义上的情况,但对于 C++ 开发人员来说可能是一个有用的类比。

在标准 jvm 上,考虑 jit 的片段所需的时间设置为-XX:CompileThreshold默认为 1500。(来源和 jvm 版本有所不同 - 但我认为这适用于 jvm8)

此外,我手头的一本书在 Host Performace JIT Chapter (p59) 下指出,在 JIT 期间完成了以下优化:

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

编辑:

关于评论

我认为 1500 可能足以向 JIT 暗示它应该将代码编译为本机并停止解释。你同意吗?

我不知道它是否只是一个提示,但由于 openjdk 是开源的,让我们看看 globals.hpp#l3559@ver-a801bc33b08c 中的各种限制和数字(对于jdk8u

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

将代码编译为本机并不一定意味着它也经过优化。

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

因此,一个计时器开始对经常访问的本机代码进行采样并对其进行优化。你知道我们如何控制这个计时器间隔吗?

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

于 2016-03-24T19:56:19.793 回答