首页 > 解决方案 > 为什么 Hotspot JIT 不对长计数器执行循环展开?

问题描述

我刚刚阅读了 Java Magazine 文章Loop Unrolling。在那里,作者证明了for带有int计数器的简单循环是使用循环展开优化编译的:

private long intStride1()
{
    long sum = 0;
    for (int i = 0; i < MAX; i += 1)
    {
        sum += data[i];
    }
    return sum;
}

但是,他们随后通过将计数器类型切换为来表明一切都发生了变化long

private long longStride1()
{
    long sum = 0;
    for (long l = 0; l < MAX; l++)
    {
        sum += data[(int) l];
    }
    return sum;
}

这会通过以下方式更改输出程序集:

  1. 介绍安全点
  2. 不执行展开

这具有显着降低吞吐量性能的效果。

为什么 64 位 HotSpot VM 不为long计数器执行循环展开?为什么第二种情况需要安全点,而第一种不需要?

标签: javacompiler-optimizationjitjvm-hotspotloop-unrolling

解决方案


从 JDK 16 开始,HotSpot JVM 支持循环展开和其他具有 64 位计数器的循环优化。

JDK-8223051的描述回答了您的两个问题:

许多核心循环转换适用于计数循环,即具有计算行程计数的循环。转换包括展开、迭代范围拆分(数组 RCE)和条带挖掘(JDK-8186027)。优化器执行许多复杂的模式匹配来检测和转换计数循环。

大多数或所有这些模式匹配和转换都适用于具有 32 位控制变量和算术的循环。只要批量操作仅适用于 Java 数组,这是有道理的,因为这些数组只能跨越 31 位索引范围。用于较大批量数据块的较新 API 将引入 64 位索引,例如巴拿马的本机数组和(可能)范围扩展的字节缓冲区。在底层,Unsafe API 通常使用 64 位地址和地址算法。处理此类数据结构的循环自然使用 64 位值,或者作为直接的 Java long,或者作为带有递增 long 组件(巴拿马指针)的包装游标结构。

需要有一个故事来改变这种长期运行的循环。此 RFE 是对该故事的请求。

一个复杂的因素是,有时计数循环没有安全点,假设最大可能的迭代(跨越 32 位动态范围)不会导致 JVM 的安全点机制由于非响应线程卡在这样的计数中而发生故障环形。这个假设在 64 位的情况下是无效的。幸运的是,我们有一个(相对较新的)优化可以解决这个问题,通过将一个非常长的运行循环剥离成一个具有适当限制的行程计数的循环序列(外循环)。


推荐阅读