跳到主要内容

JEP 285:旋转等待提示

概括

定义一个 API 以允许 Java 代码提示正在执行旋转循环。

目标

定义一个 API,允许 Java 代码向运行时系统提示它处于自旋循环中。该 API 将是纯粹的提示,并且不会携带任何语义行为要求(例如,无操作是有效的实现)。允许 JVM 从自旋循环特定行为中受益,这些行为在某些硬件平台上可能有用。在 JDK 中提供无操作实现和内在实现,并在至少一个主要硬件平台上展示执行优势。

非目标

查看自旋循环之外的性能提示并不是我们的目标。其他性能提示(例如预取提示)超出了本 JEP 的范围。

动机

一些硬件平台受益于自旋循环正在进行的软件指示。可以观察到一些常见的执行优势:

  1. 当由于各种因素而使用旋转提示时,旋转循环的反应时间可能会得到改善,从而减少旋转等待情况下线程到线程的延迟;

  2. 自旋循环中涉及的核心或硬件线程消耗的功率可以减少,有利于程序的整体功耗,并且可能允许其他核心或硬件线程在相同的功耗范围内以更快的速度执行。

虽然长期旋转作为一般用户模式编程实践通常不被鼓励,但阻塞之前的短期旋转是一种常见的做法(无论是在 JDK 内部还是外部)。此外,由于富含核心的计算平台普遍可用,许多性能和延迟敏感的应用程序(例如 Disruptor)使用一种模式,将旋转线程专用于延迟关键功能,并且还可能涉及长期旋转。

作为一个实际示例和用例,当前的 x86 处理器支持PAUSE可用于指示旋转行为的指令。使用PAUSE指令可以明显减少线程到线程的往返次数。由于其优点和广泛推荐的使用,x86PAUSE指令通常用于内核自旋锁、在阻塞之前执行启发式自旋的 POSIX 库,甚至由 JVM 本身使用。然而,由于无法提示 Java 循环正在旋转,因此常规 Java 代码无法获得其好处。

我们提供了具体的支持证据:在 E5-2697 v2 上执行的简单测试中,测量通过在易失性字段上旋转进行通信的两个线程之间的往返延迟行为,在广泛的范围内,往返延迟明显减少了 18-20 纳秒。百分位数谱(从 10%'ile 到 99.9%'ile)。这种减少可以代表在最佳情况下线程到线程通信延迟高达 35%-50% 的改进,例如,当两个旋转线程在共享物理 CPU 核心和 L1 数据缓存的两个硬件线程上执行时。可以在此处找到测试的完整列表。

spinLoopHint()上图显示了一个示例延迟测量,比较了包含内部调用(内部化为指令)的自旋循环PAUSE与不使用指令执行的同一循环的反应延迟PAUSE,以及执行实际操作所需的时间的测量。System.nanoTime()打电话来测量时间。

描述

我们建议向 JDK 添加一个方法,该方法将提示正在执行旋转循环:java.lang.Thread.onSpinWait()

空方法将是该java.lang.Thread.onSpinWait()方法的有效实现,但内部实现是可以从中受益的硬件平台的明显目标。我们打算为 JDK 生成一个内在的 x86 实现,作为此 JEP 的一部分。原型实现已经存在,并且初步测试的结果显示出希望。请参阅JBS bug JDK-8147844,获取指向 webrevs 的指针以及类库和 JVM 中建议的更改。

备择方案

JNI 可用于通过自旋循环提示 CPU 指令进行循环,但是 JNI 边界交叉开销往往大于指令提供的好处,至少在延迟方面如此。

我们可以尝试让 JIT 编译器推断自旋循环情况,并自动包含自旋循环提示 CPU 指令,而不需要 Java 代码提示。我们怀疑自动且可靠地检测旋转情况的复杂性,再加上在某些平台上使用提示的潜在权衡问题,将大大延迟可行实现的可用性。

测试

“普通”无操作实现的测试显然相当简单。

我们相信,鉴于此 API 的占用空间非常小,对内在 x86 实现的测试也将很简单。我们希望测试重点是确认代码生成的正确性和使用自旋循环提示与内在实现的延迟优势。

如果该 API 被接受为 Java SE API(例如,包含在java.*未来 Java SE 9 或 Java SE 10 的命名空间中),我们希望为该 API 开发相关的 TCK 测试,以便可能包含在 Java SE TCK 中。

风险和假设

“普通”无操作实施显然风险相当低。内在 x86 实现将涉及对多个 JVM 组件的修改,因此它们会带来一些风险,但不会比添加到 JDK 中的其他简单内在函数更多。