【问题标题】:Would the compiler (or the JVM) know to optimise this piece of code? how do I check?编译器(或 JVM)会知道优化这段代码吗?我该如何检查?
【发布时间】:2013-12-16 21:12:20
【问题描述】:

假设我有这门课

Util
{
    public static void doSomething()
    {
         if (FLAG) foo();
         else bar();
    }

    public static void foo() { /* do something */ }
    public static void bar() { /* do something else */ }

    public static final boolean FLAG = computeFlag();
    private static boolean computeFlag() { /* do some computation during init time*/ }
}

FLAG 显然永远不会改变。 并假设Util.doSomething() 被使用很多(在许多关键的地方,性能确实很重要)。 Java 编译器或 JVM 是否足够聪明地缓存 doSomething 的主体,这样代码就不必重新评估 FLAG 或重新执行分支指令?

如何检查?

谢谢

【问题讨论】:

  • 编译器,没有。也许是 JVM。
  • 如果一个新的 CPU 的分支预测器的值永远不会改变,那么它的分支预测器将会撕裂它。
  • 哎呀,对不起,我不是指编译器。我的意思是 JVM,是的。
  • 我确实改变了它。 (也可能是编译器,如果某个编译器足够聪明,可以静态评估 computeFlag 的主体并看到该值在编译时已知,因此 FLAG 的值在编译时也是已知的时间...:D)
  • 它在运行时是最终的,而不是编译时。如果 computeFlag 理论上可能会受到运行时确定的任何东西的影响,则编译器无法在编译时确定该值。如果没有,你应该弄清楚并直接分配它。

标签: java compiler-construction compiler-optimization


【解决方案1】:

这可能取决于您使用的 JVM。对于 Oracle Hotspot JVM,您可以使用

检查生成的机器代码
java -server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly your.MainClass

如果您在库路径中有必要的本机库(documentation 提到您可以从哪里获得此类二进制文件)。

运行课程:

public class DecompileTest {

    public static void doSomething() {
        if (FLAG)
            foo();
        else
            bar();
    }

    static int fooCount;

    public static void foo() {
        fooCount++;
    }

    public static void bar() {
        fooCount--;
    }

    public static final boolean FLAG = computeFlag();

    private static boolean computeFlag() {
        System.out.println("Shall I set the flag? (y/n)");
        try {
            return System.in.read() == 'y';
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            doSomething();
        }
        System.out.println(fooCount);
    }   
}

java version "1.7.0_21"
Java(TM) SE Runtime Environment (build 1.7.0_21-b11)
Java HotSpot(TM) Server VM (build 23.21-b01, mixed mode)

在我的 Intel cpu 上产生了一个冗长的 disassabmly,其中相关部​​分显示:

Decoding compiled method 0x009ca408:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} 'doSomething' '()V' in 'stackoverflow/DecompileTest'
  #           [sp+0x10]  (sp of caller)
  0x009ca500: sub    $0xc,%esp
  0x009ca506: mov    %ebp,0x8(%esp)     ;*synchronization entry
                                        ; - stackoverflow.DecompileTest::doSomething@-1 (line 8)
  0x009ca50a: mov    $0x295dc208,%ebx   ;   {oop(a 'java/lang/Class' = 'stackoverflow/DecompileTest')}
  0x009ca50f: incl   0x70(%ebx)         ;*putstatic fooCount
                                        ; - stackoverflow.DecompileTest::foo@5 (line 17)
                                        ; - stackoverflow.DecompileTest::doSomething@6 (line 9)
  0x009ca512: add    $0x8,%esp
  0x009ca515: pop    %ebp
  0x009ca516: test   %eax,0x950000      ;   {poll_return}
  0x009ca51c: ret    
  0x009ca51d: hlt    
  0x009ca51e: hlt    
  0x009ca51f: hlt    
[Exception Handler]
[Stub Code]
  0x009ca520: jmp    0x009c78c0         ;   {no_reloc}
[Deopt Handler Code]
  0x009ca525: push   $0x9ca525          ;   {section_word}
  0x009ca52a: jmp    0x009ae280         ;   {runtime_call}
  0x009ca52f: hlt    

FLAG的测试和bar()的调用都作为死代码被淘汰,foo的方法体被内联。

【讨论】:

  • +1(如果可以的话,我会给你+100!)获取有关如何进行拆卸的文档。 :)
【解决方案2】:

是的,这种代码很容易成为 HotSpot 死代码消除的目标。即使FLAG可能发生变化,HotSpot也会通过profiling判断,在实际执行中始终保持不变,并剔除未取分支。

参考:PerformanceTacticIndex

【讨论】:

  • 是的,JVM 可以将这样的标志内联到代码中。事实上,它也会对非最终标志执行此操作。 vanillajava.blogspot.co.uk/2012/01/…
  • “即使FLAG 可能发生变化,HotSpot 也会……消除未采取的分支”:我不太同意。作为一种推测性优化,生成的机器代码必须至少测试一次条件,以便在需要时取消优化,因此生成的机器代码仍将包含分支指令。事实上,如果我删除 final 并在我的答案中运行测试代码,FLAG 将被测试,如果为 false 则分支。事实上,如果 FLAG 不是最终的,即使对 bar() 的调用仍然是生成的机器代码的一部分。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-11
  • 2018-05-02
  • 1970-01-01
  • 1970-01-01
  • 2011-10-30
  • 2018-07-06
相关资源
最近更新 更多