java - 奇数表现(盒装整数)

标签 java performance optimization jit

我遇到过一个案例,我认为 JIT 应该可以很容易地进行时间优化,但事实并非如此。

我已将问题简化为一个最小的示例:

考虑一个类 IntArrayWrapper:

class IntArrayWrapper {
    private int[] data = new int[100000];
    public void setInteger(int i, Integer x) { data[i] = x; }
    public void setInt    (int i, int x)     { data[i] = x; }
}

这两种方法之间的唯一区别是 xInteger(盒装)或 int(原始)。

我已经编写了一些 JMH 基准来衡量这两种方法的性能:

@Benchmark
public void bmarkSetIntConst() {
    final IntArrayWrapper w = new IntArrayWrapper();
    for (int i = 0; i < 100000; i++) {
        w.setInt(i, 100);
    }
}

@Benchmark
public void bmarkSetIntStair() {
    final IntArrayWrapper w = new IntArrayWrapper();
    for (int i = 0; i < 100000; i++) {
        w.setInt(i, i);
    }
}

// omitted: bmarkSetIntegerConst and bmarkSetIntStair that use .setInteger(..)

预期结果

我希望看到的是:

  • setIntegerConst 等于 setIntConst这是真的。
  • setIntegerStair 等于 setIntStair这不是真的。

我认为的原因是我认为 JIT 应该内联 setInteger 调用,并意识到有一个自动装箱操作(来自调用),紧接着是一个拆箱操作(来自数组分配),因此能够删除装箱/拆箱。

这似乎是的情况。

一些观察

  • const 操作执行得同样好,我认为这是因为缓存了硬编码整数。
  • 奇怪的是 setIntStairsetIntConst 有不同的性能,我感觉 JIT 可能会在这里生成 SIMD 代码,但我将不胜感激任何见解。

结果

这些是结果,整个代码在这里:https://gist.github.com/kaeluka/fe1210074038424c30db7a52ac5c2d7b

Benchmark                             Mode  Cnt      Score     Error  Units
MyBenchmark.bmarkSetIntConst         thrpt   20  15717.814 ± 362.137  ops/s
MyBenchmark.bmarkSetIntegerConst     thrpt   20  15814.296 ± 657.945  ops/s
MyBenchmark.bmarkSetIntStair         thrpt   20  11941.879 ± 200.335  ops/s
MyBenchmark.bmarkSetIntegerStair     thrpt   20   2981.398 ±  48.806  ops/s
MyBenchmark.bmarkSetIntSawtooth      thrpt   20  11072.882 ± 234.686  ops/s
MyBenchmark.bmarkSetIntegerSawtooth  thrpt   20  11105.272 ± 156.496  ops/s

问题

  • 为什么 JIT 不能省略装箱?
  • 有没有办法在更改setInteger 接口(interface)以获取int 的情况下解决此问题? (我的原始代码使用泛型,所以 int 不是一个选项,除非我想复制很多代码)。

编辑

添加了 bmarkSetIntegerSawtoothbmarkSetIntSawtooth 的结果,将值设置为 i % 128 以衡量对象池对 Integer 的影响s.

最佳答案

Why is the JIT not able to elide boxing?

我猜 JIT 并不专门针对装箱操作,而是依靠定期的逃逸分析来消除不必要的装箱。逃逸分析对数据流相当挑剔,我怀疑问题是您的一些 装箱操作命中了Integer 缓存。潜在地从缓存中提取值可能是阻止消除装箱的原因。

我以两种方式修改了您的测试,并测量了每种方式的结果。结果似乎证实了我的假设。

  1. 我首先尝试重写您的基准测试以使用装箱的 double 值而不是 int 值,因为 double 装箱不涉及任何缓存。

  2. 然后我回到了基于 int 的基准测试,但是修改了你的循环以从 i = 128 开始,这样你的装箱操作就不会命中缓存。

在这两种情况下,性能差距都在误差范围内。

为了确认,我启用了 -XX:+PrintAssembly 来查看我修改后的基准测试是如何编译的。对于每对基准测试,装箱和原始变体具有几乎相同的指令序列。只有细微差别,例如,一对指令翻转了。看起来拳击确实被优化掉了。

解决方法:由于绕过缓存似乎可以避免这个问题,而且没有办法强制一个空的整数缓存,一个解决方法是用 new Integer(i) 替换隐式装箱。但是请注意,如果逃逸分析没有替换分配(由于达到各种编译器阈值之一),那么您的性能实际上可能降低


修改后的基准:

class IntArrayWrapper {
    private int[] data = new int[100000];
    void setBoxed(int i, Integer x) { data[i] = x; }
    void setUnboxed(int i, int x) { data[i] = x; }
}

class DoubleArrayWrapper {
    private double[] data = new double[100000];
    void setBoxed(int i, Double x) { data[i] = x; }
    void setUnboxed(int i, double x) { data[i] = x; }
}

@State(Scope.Benchmark)
public class BoxingBenchmarks {
    @Benchmark
    public void intBoxed() {
        final IntArrayWrapper w = new IntArrayWrapper();
        for (int i = 128; i < 100000; i++) w.setBoxed(i, i);
    }

    @Benchmark
    public void intUnboxed() {
        final IntArrayWrapper w = new IntArrayWrapper();
        for (int i = 128; i < 100000; i++) w.setUnboxed(i, i);
    }

    @Benchmark
    public void doubleBoxed() {
        final DoubleArrayWrapper w = new DoubleArrayWrapper();
        for (int i = 0; i < 100000; i++) w.setBoxed(i, (double) i);
    }

    @Benchmark
    public void doubleUnboxed() {
        final DoubleArrayWrapper w = new DoubleArrayWrapper();
        for (int i = 0; i < 100000; i++) w.setUnboxed(i, (double) i);
    }
}

结果:

Benchmark                        Mode  Cnt      Score      Error  Units
BoxingBenchmarks.doubleBoxed    thrpt    5   6513.760 ± 1075.605  ops/s
BoxingBenchmarks.doubleUnboxed  thrpt    5   6883.235 ±  414.803  ops/s
BoxingBenchmarks.intBoxed       thrpt    5  10902.200 ±  315.437  ops/s
BoxingBenchmarks.intUnboxed     thrpt    5  11148.648 ±  935.877  ops/s

关于java - 奇数表现(盒装整数),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50046221/

相关文章:

java - 使用 JavaMail 时避免 NoClassDefFoundError

ios - 为什么 Rand() 总是生成具有固定唯一组计数的随机数?

mysql - 优化表有什么作用,或者如何正确优化磁盘上的 PK

c++ - 现代 C++ 编译器的有效优化策略

java - 如何读取带有空格的文本文件 android java

java - 无法让 PowerShell 读取 * 字符

php - 使用有限的内存处理来自 mysql 的大型结果集

c++ - 这是在 Windows 中将大型 DIB 绘制到屏幕上的最快方法

c - 如何将 SSE 应用于长度不能保证为 4 的倍数的数组?

java - 将 JNA 指针从一个 Java 应用程序发送到另一个 Java 应用程序