3

我正在对方法的性能进行调查,最终确定开销是由 if else 语句的“else”部分引起的。我编写了一个小程序来说明性能差异,即使代码的 else 部分从未被执行:

public class TestIfPerf
{
    public static void main( String[] args )
    {   
        boolean condition = true; 
        long time = 0L;
        int value = 0;
        // warm up test 
        for( int count=0; count<10000000; count++ )
        {       
            if ( condition ) 
            {           
                value = 1 + 2;  
            }           
            else        
            {           
                value = 1 + 3;  
            }           
        }       
        // benchmark if condition only
        time = System.nanoTime();
        for( int count=0; count<10000000; count++ )
        {
            if ( condition )
            {
                value = 1 + 2;
            }           
        }
        time = System.nanoTime() - time; 
        System.out.println( "1) performance " + time ); 
        time = System.nanoTime();
        // benchmark if else condition
        for( int count=0; count<10000000; count++ )
        {
            if ( condition )
            {
                value = 1 + 2;
            }           
            else
            {
                value = 1 + 3;
            }
        }
        time = System.nanoTime() - time; 
        System.out.println( "2) performance " + time ); 
    }   
}

并运行测试程序java -classpath . -Dmx=800m -Dms=800m TestIfPerf

我在 Mac 和 Linux Java 上使用 1.6 最新版本执行了此操作。始终如一地,没有 else 的第一个基准测试比有 else 部分的第二个基准测试快得多,即使代码的结构使得 else 部分由于条件而永远不会执行。我知道对某些人来说,差异可能并不显着,但相对性能差异很大。我想知道是否有人对此有任何见解(或者也许我做错了什么)。


Linux 基准测试(纳米)

  1. 性能 1215488
  2. 性能 2629531

Mac 基准测试(纳米)

  1. 性能 1667000
  2. 性能 4208000
4

6 回答 6

6

你的测试是假的。如果我交换测试条件,我会得到完全相反的结果:

1) performance 5891113
2) performance 15216601

2) performance 5428062
1) performance 15087676

它可能与 JVM 在执行过程中优化代码有关。如果我多次复制/粘贴条件,我会得到:

2) performance 6258694
1) performance 34484277
2) performance 978
1) performance 978
2) performance 908
于 2012-12-15T23:14:29.360 回答
2

有两种可能的解释:

  • 你得到的时间被基准缺陷所扭曲。您做错了很多事情 - 请参阅如何在 Java 中编写正确的微基准测试?

  • 真正的版本else每次循环迭代花费的时间稍长。如果是这种情况,则有多种可能的解释。处理它的最佳方法是查看 JIT 编译器生成的本机代码并分析其性能。

但最重要的是,这既不令人惊讶(见上文),也不会对绝大多数 Java 应用程序产生任何实际影响。由应用程序确定是否需要“if then”或“if then else”。

值得怀疑的是,你可能从这样的人工微基准中学到的任何东西对真正的代码有指导意义。JIT 编译器可能会在比您的测试执行的更复杂的级别上优化代码。您在此处可能看到的内容(如果您的基准测试没有缺陷)不太可能反映在实际应用程序中。

于 2012-12-15T23:00:31.187 回答
0

Java code:

        for (int count = 0; count < 10000000; count++) {
            // start of the if
            if (cond) {
                value = 1 + 2;
            }
            // end of the if
        }

Java bytecode:

   7:   lstore_3      //store a long value in a local variable 3
   8:   iconst_0      //load the int value 0 onto the stack
   9:   istore  5     //store int value into variable #index
   11:  goto    23    //goes to another instruction at branchoffset
   14:  iload_1       //load an int value from local variable 1
   15:  ifeq    20    //if value is 0, branch to instruction at branchoffset
   18:  iconst_3      //load the int value 3 onto the stack
   19:  istore_2      //store int value into variable 2
   20:  iinc    5, 1  //increment local variable #index by signed byte const
   23:  iload   5     //load an int value from a local variable #index
   25:  ldc #22;  //push a constant #index from a constant pool (String, int or float) onto the stack - int 10000000
   27:  if_icmplt 14  //if value1 is less than value2, branch to instruction at branchoffset

Java code:

        for (int count = 0; count < 10000000; count++) {
            // start of the if
            if (cond) {
                value = 1 + 2;
            } else {
                value = 1 + 3;
            }
            // end of the if
        }

Java bytecode:

   63:  lstore_3      //store a long value in a local variable 3
   64:  iconst_0      //load the int value 0 onto the stack
   65:  istore  5     //store int value into variable #index
   67:  goto    84    //goes to another instruction at branchoffset
   70:  iload_1       //load an int value from local variable 1
   71:  ifeq    79    //if value is 0, branch to instruction at branchoffset
   74:  iconst_3      //load the int value 3 onto the stack
   75:  istore_2      //store int value into variable 2
   76:  goto    81    //goes to another instruction at branchoffset
   79:  iconst_4      //load the int value 4 onto the stack
   80:  istore_2      //store int value into variable 2
   81:  iinc    5, 1  //increment local variable #index by signed byte const
   84:  iload   5     //load an int value from a local variable #index
   86:  ldc #22;  //push a constant #index from a constant pool (String, int or float) onto the stack - int 10000000
   88:  if_icmplt 70  //if value1 is less than value2, branch to instruction at branchoffset
于 2012-12-16T09:17:41.427 回答
0

如果您在第二次测量中注释掉 else 分支,它会变得更加奇怪:它仍然更慢,尽管它现在是相同的代码。好消息是,如果您在单独的方法中提取此代码,第二次运行速度会快得多。

我唯一能想到的是JVM只优化了长方法的第一部分。是的:如果我把 if-else 测量放在第一位,它会更快。

于 2012-12-15T23:03:12.723 回答
0

必须与 VM init(预热时间很短)或时间测量中的抖动(与 VM 启动有关)有关。

如果你交换循环,循环 2 会更快:-)

一般来说,热点 JIT 是不错的,但不可靠,也不是确定性的。在java中获得最佳性能

  • 避免创建对象
  • 尽可能将 vars 标记为 final。有时这并没有什么不同,但有时确实如此。在循环中访问实例变量时,将它们复制到最终本地。
  • 如果可能,将方法标记为最终方法。
  • 如果您想要一种内联方法,请保持简短,也许可以将其分解。我通过测试多次验证了这一点,通过拆分“void foo() { if (..) return ..stuff ... }” 像“foo() { if (..) return;否则 foo1() } void foo1() { 东西 }"

一般来说,使用微基准测试来证明性能模式非常困难,因为您不知道究竟是什么触发了内联、jit 编译和进一步的运行时优化。JIT 中存在阈值,因此可能会因为您在方法中添加语句或添加现有类的子类而降低性能。

于 2012-12-15T23:24:01.570 回答
-1
public class Main {

public static void main(String[] args) {
    boolean cond = true;
    int nothing = 0;

    for (int i = 0; i < 20; i++) {
        int value = 0;
        long time = System.nanoTime();
        for (int count = 0; count < 10000000; count++) {
            if (cond) {
                value = 1 + 2;
            }
        }

        time = System.nanoTime() - time;
        System.out.println("1) performance: " + time);
        nothing = value; // prevent java ignoring value

        value = 0;
        time = System.nanoTime();
        for (int count = 0; count < 10000000; count++) {
            if (cond) {
                value = 1 + 2;
            } else {
                value = 1 + 3;
            }
        }
        time = System.nanoTime() - time;
        System.out.println("2) performance: " + time);
        nothing = value; // prevent java ignoring value
    }

    nothing = nothing + 1;
}
}

这是结果:

1) performance: 1797000
2) performance: 3742000
1) performance: 7290000
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 1000
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 1000
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
1) performance: 0
2) performance: 0
于 2012-12-16T01:02:01.453 回答