为什么Hotspot JIT不对长计数器执行循环展开?

7

我刚刚阅读了《Java杂志》的文章《循环展开》。在那里,作者展示了简单的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. 引入Safepoints
  2. 不执行unrolling(循环展开优化)

这会极大地降低吞吐量性能。

为什么64位HotSpot VM不对long计数器执行循环展开优化?为什么第二种情况需要Safepoints,而第一种情况则不需要?


5
这个问题已经在 JDK 16 中得到修复。 - apangin
2个回答

9

自JDK 16起,HotSpot JVM支持对64位计数器的循环展开和其他优化。

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

许多核心循环变换适用于计数循环,即带有计算行程计数的循环。这些变换包括展开、迭代范围分割(数组RCE)和条带挖掘(JDK-8186027)。优化器执行许多复杂的模式匹配来检测和转换计数循环。
大多数或所有这些模式匹配和变换都适用于32位控制变量和算术的循环。只要批量操作仅适用于Java数组,这就是有意义的,因为这些数组只能跨越31位索引范围。新的API用于更大块的批量数据将引入64位索引,例如Panama的本机数组和(可能的)范围扩展字节缓冲区。在底层,Unsafe API通常使用64位地址和地址算术。对此类数据结构进行操作的循环自然使用64位值,既可以作为直接的Java长整型,也可以作为具有递增长组件的包装光标结构(Panama指针)。
需要有一个故事来转换这样长时间运行的循环。这个RFE是对那个故事的请求。
一个复杂的因素是,有时计数循环没有安全点,因为假定最大可能的迭代(跨越32位的动态范围)不会导致JVM的安全点机制因非响应线程而在这样的计数循环中发生故障。在64位情况下,这个假设是无效的。幸运的是,我们有一个(相对较新的)优化可以解决这个问题,通过将单个非常长时间运行的循环条带挖掘成具有适当有界行程计数的循环序列(外部循环)。

0

这是因为可能存在整数溢出的跟踪。将int强制转换为long并检查最小/最大int时,很容易捕获整数溢出,但在大多数平台上没有比long更大的类型。 但自JDK 16以来,支持长循环。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接