【问题标题】:Does Java's JIT compiler "expand" trivial loops?Java 的 JIT 编译器会“扩展”普通循环吗?
【发布时间】:2016-09-17 03:15:39
【问题描述】:

我正在编写我的应用程序的性能敏感部分,我很好奇 JIT 编译器(如果有的话)将如何优化以下方法:

private static int alphaBlend(int foreground, int background) {
    int alpha = (foreground >> 24) & 0xFF;
    int subAlpha = 0xFF - alpha;
    return ((((((foreground >> 16) & 0xFF) * alpha) + (((background >> 16) & 0xFF)) * subAlpha) >> 8) << 16)
            | ((((((foreground >> 8) & 0xFF) * alpha) + (((background >> 8) & 0xFF)) * subAlpha) >> 8) << 8)
            | ((((foreground & 0xFF) * alpha) + ((background & 0xFF)) * subAlpha) >> 8);
}

private static int alphaBlendLoop(int foreground, int background) {
    int alpha = (foreground >> 24) & 0xFF;
    int subAlpha = 0xFF - alpha;
    int blended = 0;
    for (int shift = 16; shift >= 0; shift -= 8) {
        blended |= (((((foreground >> shift) & 0xFF) * alpha) + (((background >> shift) & 0xFF)) * subAlpha) >> 8) << shift;
    }
    return blended;
}

这些方法执行alpha 混合。基本上,它们将前景 RGBA 像素与背景 RGB 像素相结合,其 RGB 分量值已与 alpha 值相乘。

这两种方法都为相同的输入返回相同的值,但它们的实现不同。 个人,我发现后一种实现更易于阅读,但我担心它的性能可能会降低。两种实现的字节码都包含在下面供感兴趣的人使用(它是使用 IntelliJ 的“显示字节码”视图生成的):

  private static alphaBlend(II)I
   L0
    LINENUMBER 95 L0
    ILOAD 0
    BIPUSH 24
    ISHR
    SIPUSH 255
    IAND
    ISTORE 2
   L1
    LINENUMBER 96 L1
    SIPUSH 255
    ILOAD 2
    ISUB
    ISTORE 3
   L2
    LINENUMBER 97 L2
    ILOAD 0
    BIPUSH 16
    ISHR
    SIPUSH 255
    IAND
    ILOAD 2
    IMUL
    ILOAD 1
    BIPUSH 16
    ISHR
    SIPUSH 255
    IAND
    ILOAD 3
    IMUL
    IADD
    BIPUSH 8
    ISHR
    BIPUSH 16
    ISHL
    ILOAD 0
    BIPUSH 8
    ISHR
    SIPUSH 255
    IAND
    ILOAD 2
    IMUL
    ILOAD 1
    BIPUSH 8
    ISHR
    SIPUSH 255
    IAND
    ILOAD 3
    IMUL
    IADD
    BIPUSH 8
    ISHR
    BIPUSH 8
    ISHL
    IOR
    ILOAD 0
    SIPUSH 255
    IAND
    ILOAD 2
    IMUL
    ILOAD 1
    SIPUSH 255
    IAND
    ILOAD 3
    IMUL
    IADD
    BIPUSH 8
    ISHR
    IOR
    IRETURN
   L3
    LOCALVARIABLE foreground I L0 L3 0
    LOCALVARIABLE background I L0 L3 1
    LOCALVARIABLE alpha I L1 L3 2
    LOCALVARIABLE subAlpha I L2 L3 3
    MAXSTACK = 4
    MAXLOCALS = 4

  private static alphaBlendLoop(II)I
   L0
    LINENUMBER 103 L0
    ILOAD 0
    BIPUSH 24
    ISHR
    SIPUSH 255
    IAND
    ISTORE 2
   L1
    LINENUMBER 104 L1
    SIPUSH 255
    ILOAD 2
    ISUB
    ISTORE 3
   L2
    LINENUMBER 105 L2
    ICONST_0
    ISTORE 4
   L3
    LINENUMBER 106 L3
    BIPUSH 16
    ISTORE 5
   L4
   FRAME FULL [I I I I I I] []
    ILOAD 5
    IFLT L5
   L6
    LINENUMBER 107 L6
    ILOAD 4
    ILOAD 0
    ILOAD 5
    ISHR
    SIPUSH 255
    IAND
    ILOAD 2
    IMUL
    ILOAD 1
    ILOAD 5
    ISHR
    SIPUSH 255
    IAND
    ILOAD 3
    IMUL
    IADD
    BIPUSH 8
    ISHR
    ILOAD 5
    ISHL
    IOR
    ISTORE 4
   L7
    LINENUMBER 106 L7
    IINC 5 -8
    GOTO L4
   L5
    LINENUMBER 109 L5
   FRAME CHOP 1
    ILOAD 4
    IRETURN
   L8
    LOCALVARIABLE shift I L4 L5 5
    LOCALVARIABLE foreground I L0 L8 0
    LOCALVARIABLE background I L0 L8 1
    LOCALVARIABLE alpha I L1 L8 2
    LOCALVARIABLE subAlpha I L2 L8 3
    LOCALVARIABLE blended I L3 L8 4
    MAXSTACK = 4
    MAXLOCALS = 6

直观地说,循环需要更多的“工作”和操作(跳转、评估条件、递减等)。但是,循环也非常可预测;它总是会精确执行 3 次,并且在其范围内定义的变量将始终具有相同的三个值。

在这种情况下,JIT 编译器(或 smarter 静态编译器?)是否能够通过将其扩展为可能像在alphaBlend 实施?还是循环通常无法以这种方式优化?

【问题讨论】:

  • 在决定“优化”您的代码之前,使用实际运行的代码衡量性能。

标签: java performance optimization jit


【解决方案1】:

是的,HotSpot JIT 支持循环展开常量传播优化,可以将alphaBlendLoop 转换为类似于手动展开的alphaBlend

我个人更喜欢第三种选择:一个使代码更具可读性的小辅助函数:

private static int blend(int foreground, int background, int alpha, int shift) {
    int fg = (foreground >>> shift) & 0xff;
    int bg = (background >>> shift) & 0xff;
    return (fg * alpha + bg * (256 - alpha)) >>> 8 << shift;
}

public static int alphaBlend(int foreground, int background) {
    int alpha = foreground >>> 24;
    int R = blend(foreground, background, alpha, 0);
    int G = blend(foreground, background, alpha, 8);
    int B = blend(foreground, background, alpha, 16);
    return R | G | B;
}

我创建了一个JMH benchmark 来验证所有 3 个选项的性能是否相似。
在 Java 8u77 x64 上测试。

Benchmark               Mode  Cnt  Score   Error  Units
Blend.alphaBlendInline  avgt   10  7,831 ± 0,045  ns/op
Blend.alphaBlendLoop    avgt   10  7,860 ± 0,025  ns/op
Blend.alphaBlendMethod  avgt   10  7,769 ± 0,056  ns/op

【讨论】:

    【解决方案2】:

    对于 Oracle VM,您可以使用 -XX:+PrintAssembly 检查 JIT 输出。

    (要让 JIT 启动,您的测试程序应该调用感兴趣的方法至少 10 000 次。)

    【讨论】:

      猜你喜欢
      • 2011-09-02
      • 1970-01-01
      • 2011-02-11
      • 2011-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-04
      • 2016-05-25
      相关资源
      最近更新 更多