JEP 285:自旋等待提示
概述
定义一个 API,以允许 Java 代码提示正在执行自旋循环。
目标
定义一个 API,该 API 将允许 Java 代码向运行时系统提示其正处于自旋循环(spin loop)中。此 API 将是一个纯粹的提示,不带有任何语义行为要求(例如,无操作是一个有效的实现)。使 JVM 能够利用在某些硬件平台上可能有用的特定于自旋循环的行为。在 JDK 中同时提供一个无操作实现和一个内联实现,并至少在一个主要硬件平台上展示执行性能的提升。
关键点
- API 的作用:提示运行时系统当前处于自旋循环。
- 语义要求:无强制语义行为要求,允许无操作实现。
- 目标:利用特定硬件平台上的优化行为。
- 实现:
- 提供无操作(no-op)实现。
- 提供内联(intrinsic)实现。
- 验证:在至少一个主要硬件平台上证明性能改进。
非目标
查看自旋循环以外的性能提示并不是目标。其他性能提示(例如预取提示)不在该 JEP 的范围内。
动机
一些硬件平台得益于软件表明正处于自旋循环中。可以观察到一些常见的执行优势:
-
由于各种因素,在使用自旋提示(spin hint)时,自旋循环的反应时间可能会得到改善,从而减少在自旋等待情况下的线程间延迟;
-
参与自旋循环的核心或硬件线程所消耗的功率可能会降低,这有助于程序的整体功耗,并可能允许其他核心或硬件线程在相同的功耗范围内以更快的速度执行。
虽然长期自旋通常不被推荐为通用的用户模式编程实践,但在阻塞前进行短期自旋是一种常见做法(无论是在 JDK 内部还是外部)。此外,随着多核计算平台的普及,许多对性能和延迟敏感的应用程序(例如 Disruptor)采用了一种模式,即为延迟关键功能分配一个自旋线程,并且可能还涉及长期自旋。
作为一个实际的例子和用例,当前的 x86 处理器支持一种 PAUSE
指令,可以用来指示自旋行为。使用 PAUSE
指令能够显著减少线程到线程的往返时间。由于其带来的好处和广泛推荐的使用方式,x86 的 PAUSE
指令通常用于内核自旋锁、在阻塞前执行启发式自旋的 POSIX 库,甚至 JVM 本身也会使用它。然而,由于无法提示 Java 循环正在自旋,其优势并不能为普通的 Java 代码所用。
我们提供了具体的支撑证据:在 E5-2697 v2 上进行的简单测试中,测量了两个通过在易变字段上自旋来进行通信的线程之间的往返延迟行为,结果表明,在广泛的百分位范围内(从 10% 到 99.9%),往返延迟明显减少了 18-20 纳秒。这种减少在最佳情况下可以代表线程间通信延迟提高了 35%-50%,例如,当两个自旋线程运行在共享一个物理 CPU 核心和 L1 数据缓存的两个硬件线程上时。该测试的完整列表可以在这里找到。
上面的图片展示了一个延迟测量的示例,比较了包含内在函数 spinLoopHint()
调用(内联为 PAUSE
指令)的自旋循环的反应延迟与未使用 PAUSE
指令执行的相同循环的延迟,并测量了执行实际 System.nanoTime()
调用以测量时间所需的时间。
描述
我们建议在 JDK 中添加一个方法,该方法会提示正在执行自旋循环:java.lang.Thread.onSpinWait()
。
一个空方法是 java.lang.Thread.onSpinWait()
方法的有效实现,但对于可以从中受益的硬件平台来说,固有实现显然是目标。我们计划作为此 JEP 的一部分为 JDK 提供一个内在的 x86 实现。原型实现已经存在,初步测试结果显示出了希望。有关类库和 JVM 中建议更改的 webrevs 指针,请参阅 JBS bug JDK-8147844。
替代方案
JNI 可以用于通过带有自旋循环提示的 CPU 指令进行循环,但至少在延迟方面,跨越 JNI 边界的开销往往大于该指令带来的好处。
我们可以尝试让 JIT 编译器推断出自旋循环的情况,并自动包含带有自旋循环提示的 CPU 指令,而无需 Java 代码提示。我们怀疑,自动且可靠地检测自旋情况的复杂性,加上在某些平台上使用这些提示可能带来的权衡问题,会显著延迟可行实现的可用性。
测试
测试一个“原味的(普通)”无操作实现显然会相当简单。
我们相信,鉴于此 API 的占用空间非常小,对内联化 x86 实现的测试也将十分简单。我们预计测试将集中于确认使用内联实现的旋转循环提示在代码生成正确性以及延迟效益方面的表现。
如果该 API 被接受为 Java SE API(例如,未来在 Java SE 9 或 Java SE 10 中包含于 java.*
命名空间),我们预计将为该 API 开发相关的 TCK 测试,以便可能将其纳入 Java SE TCK。
风险与假设
“原生”无操作实现显然风险相当低。x86 的固有实现将涉及对多个 JVM 组件的修改,因此它们带有一些风险,但不会比添加到 JDK 中的其他简单固有函数的风险更大。