【问题标题】:How does just-in-time compiler optimizes Java parallel streams?即时编译器如何优化 Java 并行流?
【发布时间】:2018-07-19 04:37:39
【问题描述】:

前段时间有人问过一个有趣的question

Can (a == 1 && a == 2 && a == 3) evaluate to true in Java?

我决定证明使用 Java 8 Stream API(确切地说是并行流)是可能的。这是我在极少数情况下有效的代码:

class Race {
    private static int a;

    public static void main(String[] args) {
        IntStream.range(0, 100_000).parallel().forEach(i -> {
            a = 1;
            a = 2;
            a = 3;
            testValue();
        });
    }

    private static void testValue() {
        if (a == 1 && a == 2 && a == 3) {
            System.out.println("Success");
        }
    }
}

然后我想,也许是因为潜在的 JIT 编译器优化?因此,我尝试使用以下 VM 选项运行代码:

-Djava.compiler=NONE

我禁用了JIT,成功案例的数量明显增加了!

即时编译器如何优化并行流,以便优化可能影响上述代码执行?

【问题讨论】:

  • 你忘了a volatile,这就是这里的区别,而不是流

标签: java parallel-processing jvm java-stream jit


【解决方案1】:

流无关紧要。仅使用两个简单的线程就可以观察到相同的效果,例如 this answer

a 不是volatile 时,JIT 编译器可以优化(而且确实如此!)连续赋值。

    a = 1;
    a = 2;
    a = 3;

转化为

    a = 3;

此外,JIT 编译器还将if (a == 1 && a == 2 && a == 3) 优化为if (false),然后将整个testValue() 调用作为死代码安全地删除。

让我们看看为 lambda 生成的程序集。
要打印编译后的代码,我使用-XX:CompileCommand=print,Race::lambda$main$0

  # {method} {0x000000001e142de0} 'lambda$main$0' '(I)V' in 'Race'
  # parm0:    rdx       = int
  #           [sp+0x20]  (sp of caller)
  0x00000000052eb740: sub     rsp,18h
  0x00000000052eb747: mov     qword ptr [rsp+10h],rbp  ;*synchronization entry
                                                ; - Race::lambda$main$0@-1 (line 8)

  0x00000000052eb74c: mov     r10,76b8940c0h    ;   {oop(a 'java/lang/Class' = 'Race')}
  0x00000000052eb756: mov     dword ptr [r10+68h],3h  ;*putstatic a
                                                ; - Race::lambda$main$0@9 (line 10)

  0x00000000052eb75e: add     rsp,10h
  0x00000000052eb762: pop     rbp
  0x00000000052eb763: test    dword ptr [3470000h],eax
                                                ;   {poll_return}
  0x00000000052eb769: ret

除了方法 prologue 和 eplilogue 之外,只有一条指令存储值 3:

  mov     dword ptr [r10+68h],3h  ;*putstatic a

因此,一旦编译方法,System.out.println 就永远不会发生。看到“成功”的极少数情况发生在解释过程中,此时代码尚未 JIT 编译。

【讨论】:

  • 如果avolatile,为什么不能将a = 1; a = 2; a = 3; 优化为a = 3;
  • @shmosel 严格来说,它可能在这种特殊情况下被优化,尽管这样的优化并不是开发人员或编译器编写者通常对volatile 变量所期望的。在 HotSpot JVM 中,易失性存储始终是对主内存的写入。
  • @shmosel 它需要额外的逻辑(考虑到对内存可见性的影响),只是为了服务于一种不常见的情况,因为开发人员已经尝试避免对volatile 变量的过多访问(通常),如他们知道自己会对性能产生影响。
猜你喜欢
  • 2018-07-08
  • 2011-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-26
  • 1970-01-01
相关资源
最近更新 更多