Java循环效率 [英] Java loop efficiency

查看:163
本文介绍了Java循环效率的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我比较了Java中的嵌套for while和do-while循环的效率,我遇到了一些奇怪的结果,我需要帮助理解。



< pre $ public class Loops {
public static void main(String [] args){
int L = 100000; //每个循环的迭代次数
// for循环
double start = System.currentTimeMillis();
long s1 = 0; (int j = 0; j s1 + = 1;
for(int i = 0; i< L +++)
}
}
double end = System.currentTimeMillis();
String result1 = String.format(for loop:%.5f,(end-start)/ 1000);
System.out.println(s1);
System.out.println(result1);

// do-while循环
double start1 = System.currentTimeMillis();
int i = 0;
long s2 = 0;
do {
i ++;
int j = 0;
do {
s2 + = 1;
j ++;
} while(j } while(i double end1 = System.currentTimeMillis();
String result2 = String.format(do-while:%.5f,(end1-start1)/ 1000);
System.out.println(s2);
System.out.println(result2);

// while循环
double start2 = System.currentTimeMillis();
i = 0;
long s3 = 0;
while(i i ++;
int j = 0; (j s3 + = 1;

j ++;
}
}
double end2 = System.currentTimeMillis();
String result3 = String.format(while:%.5f,(end2-start2)/ 1000);
System.out.println(s3);
System.out.println(result3);






$ b

所有的循环计数器总和为100亿;结果困扰我:

for循环:6.48300

do-while:0.41200



while:9.71500



为什么do-while循环要快得多?这种性能差距与对L的任何更改并行进行。我独立运行这些循环,并执行相同的操作。

我已经运行你提供的代码,也很惊讶地发现这些性能的差异。好奇心引导我开始调查,发现尽管这些循环似乎做同样的事情,他们之间有一些重要的不同。



第一次运行后我的结果这些循环是:

  for循环:1.43100 
do-while:0.51300
while:1.54500

但是当我运行这三个循环至少10次时,这些循环的性能几乎相当一样。

  for循环:0.43200 
do-while:0.46100
while:0.42900
code>

JIT能够随着时间的推移优化这些循环,但是必须有一些不同之处,导致这些循环具有不同的初始性能。实际上有两个区别:
$ b $ ul

  • do-while 循环比和 循环的做的比较少。


    为简单起见,假设L = 1 b
    $ b

      long s1 = 0; (int j = 0; j  s1 + = 1; 
    for(int i = 0; i< L +++)

    外循环:0< 1
    内循环:0< 1
    内循环:1< 1
    外循环:1< 1



    共有4笔比较

    pre $ int i = 0;
    long s2 = 0;
    do {
    i ++;
    int j = 0;
    do {
    s2 + = 1;
    j ++;
    } while(j } while(i

    内循环:1< 1
    外循环:1< 1

    总共有2笔比较
    $ b


    • 生成的字节码



    为了进一步调查,我稍微改变了你的班级,不影响它的工作方式。 / p>

      public class Loops {
    final static int L = 100000; //每个循环的迭代次数

    public static void main(String [] args){
    int round = 10;
    while(round - > 0){
    forLoop();
    doWhileLoop();
    whileLoop();



    private static long whileLoop(){
    int i = 0;
    long s3 = 0;
    while(i ++< L){
    int j = 0;
    而(j ++< L){
    s3 + = 1;
    }
    }
    return s3;


    private static long doWhileLoop(){
    int i = 0;
    long s2 = 0;
    do {
    int j = 0;
    do {
    s2 + = 1;
    } while(++ j } while(++ i 返回s2;


    private static long forLoop(){
    long s1 = 0; (int j = 0; j s1 + = 1;
    for(int i = 0; i }
    }
    返回s1;然后编译它并调用






    javap -c -s -private -l Loop获取字节码。
    $ b $首先是doWhileLoop的字节码。

      0:iconst_0 //将int值0压入堆栈
    1:istore_1 //将int值存入变量1(i)
    2:lconst_0 //将long 0推入堆栈
    3:lstore_2 //将一个long值存储在局部变量2(s2)
    4:iconst_0 //将int值0堆栈
    5:istore 4 //将int值存入变量4(j)
    7:lload_2 //从本地变量中加载一个long值2(i)
    8:lconst_1 / /将长1推入堆栈
    9:ladd //添加两个长
    10:lstore_2 //在局部变量中存储一个长整型值2(i)
    11:iinc 4, 1 //通过有符号字节1增加局部变量4(j)
    14:iload 4 //加载一个从局部变量中获取int值4(j)
    16:iload_0 //从局部变量中加载一个int值0(L)
    17:if_icmplt 7 //如果value1小于value2,指令在7
    20:iinc 1,1 //通过有符号字节1增加局部变量1(i)
    23:iload_1 //从局部变量1(i)加载一个int值
    24:iload_0 //从局部变量0(L)加载一个int值
    25:if_icmplt 4 //如果value1小于value2,则跳转到指令4
    28:lload_2 // load一个来自局部变量的long值2(s2)
    29:lreturn //返回一个长整型值



    whileLooP的字节码:

    $ $ p $ $ $ c $ 0 $ icon $ iconst_0 //将int值0压入堆栈
    1 :istore_1 //将int值存储到变量1(i)
    2:lconst_0 //将long 0推入堆栈
    3:lstore_2 //将long值存储在局部变量2中(s 3)
    4:goto 26
    7:iconst_0 //将int值0压入堆栈
    8:istore 4 //将int值存入变量4(j)
    10:goto 17
    13:lload_2 //从一个局部变量中加载一个long值2(s3)
    14:lconst_1 //将long 1推到堆栈
    15:ladd //添加两个long
    16:lstore_2 //在一个局部变量中存储一个long值2(s3)
    17:iload 4 //从局部变量中加载一个int值4(j)
    19:iinc 4,1 //通过有符号字节1增加局部变量4(j)
    22:iload_0 //从局部变量0(L)加载一个int值
    23:if_icmplt 13 //如果value1小于value2,则转到13
    的指令26:iload_1 //从局部变量1(i)加载一个int值
    27:iinc 1,1 //将局部变量1加1有符号字节1
    30:iload_0 //从局部变量0(L)加载一个int值
    31:if_icmplt 7 //如果value1小于value2,跳转到7
    指令:lload_2 //从局部变量2(s3)加载一个long值
    35:lreturn //返回a long value




    $为了使输出更具可读性,我添加了一些注释,描述每条指令在< Java字节码指令列表。



    如果仔细观察你会看到这两个字节码有一个重要的区别。
    while循环(对于for循环也是如此)具有在字节码末尾定义的if语句( if_icmplt 指令)。这意味着要检查第一个循环的退出条件,必须调用第26行的goto,并且第二个循环的goto到第17行。上面的字节码是在Mac OS X上使用javac 1.6.0_45生成的。
    $ b

    总结 / b>

    我认为在while和for循环字节码中不同的比较量加上goto指令的存在是造成这些循环之间的性能差异的原因。 p>

    I'm comparing the efficiency of nested for, while and do-while loops in Java, and I've come across some strange results that I need help understanding.

    public class Loops {
        public static void main(String[] args) {
            int L = 100000;    // number of iterations per loop
            // for loop
            double start = System.currentTimeMillis();
            long s1 = 0;
            for (int i=0; i < L; i++) {
                for (int j = 0; j < L; j++) {
                    s1 += 1;
                }
            }
            double end = System.currentTimeMillis();
            String result1 = String.format("for loop: %.5f", (end-start) / 1000);
            System.out.println(s1);
            System.out.println(result1);
    
            // do-while loop
            double start1 = System.currentTimeMillis();
            int i = 0;
            long s2 = 0;
            do {
                i++;
                int j = 0;
                do {
                    s2 += 1;
                    j++;
                } while (j < L);
            } while (i < L);
            double end1 = System.currentTimeMillis();
            String result2 = String.format("do-while: %.5f", (end1-start1) / 1000);
            System.out.println(s2);
            System.out.println(result2);
    
            // while loop
            double start2 = System.currentTimeMillis();
            i = 0;
            long s3 = 0;
            while (i < L) {
                i++;
                int j = 0;
                while (j < L) {
                    s3 += 1;
                    j++;
                }
            }
            double end2 = System.currentTimeMillis();
            String result3 = String.format("while: %.5f", (end2-start2) / 1000);
            System.out.println(s3);
            System.out.println(result3);
        }
    }
    

    All of the loops respective counters sum to 10 billion; the results perplex me:

    for loop: 6.48300

    do-while: 0.41200

    while: 9.71500

    Why is the do-while loop so much quicker? This performance gap scales in parallel with any changes to L. I've run these loops independently and they perform the same.

    解决方案

    I have run the code you have provided and also was surprised to see these differences in performance. Lead by curiosity I started investigating and found out that indeed despite these loops seem to be doing the same thing there are some important differences between them.

    My results after the first run of these loops were:

    for loop: 1.43100
    do-while: 0.51300
    while: 1.54500
    

    But when I run these three loops at least 10 times then the performance of each of these loop was pretty much the same.

    for loop: 0.43200
    do-while: 0.46100
    while: 0.42900
    

    The JIT is able to optimize these loops over time, but there must be some dissimilarity causing these loops to have a different initial performance. In fact there are actually two differences:

    • The do-while loop is doing fewer comparisons than the for and while loops

    For simplicity assume L = 1

    long s1 = 0;
    for (int i=0; i < L; i++) {
        for (int j = 0; j < L; j++) {
            s1 += 1;
    

    outer loop: 0 < 1
    inner loop: 0 < 1
    inner loop: 1 < 1
    outer loop: 1 < 1

    4 comparisons in total

    int i = 0;
    long s2 = 0;
    do {
        i++;
        int j = 0;
        do {
            s2 += 1;
            j++;
        } while (j < L);
    } while (i < L);
    

    inner loop: 1 < 1
    outer loop: 1 < 1

    2 comparisons in total

    • Different generated bytecodes

    For the purpose of further investigation I have changed your class slightly, not impacting the way it works.

    public class Loops {
        final static int L = 100000; // number of iterations per loop
    
        public static void main(String[] args) {
            int round = 10;
            while (round-- > 0) {
                forLoop();
                doWhileLoop();
                whileLoop();
            }
        }
    
        private static long whileLoop() {
            int i = 0;
            long s3 = 0;
            while (i++ < L) {
                int j = 0;
                while (j++ < L) {
                    s3 += 1;
                }
            }
            return s3;
        }
    
        private static long doWhileLoop() {
            int i = 0;
            long s2 = 0;
            do {
                int j = 0;
                do {
                    s2 += 1;
                } while (++j < L);
            } while (++i < L);
            return s2;
        }
    
        private static long forLoop() {
            long s1 = 0;
            for (int i = 0; i < L; i++) {
                for (int j = 0; j < L; j++) {
                    s1 += 1;
                }
            }
            return s1;
        }
    }
    

    Then compiled it and invoked javap -c -s -private -l Loop to get the bytecode.

    First the bytecode of doWhileLoop.

       0:   iconst_0        // push the int value 0 onto the stack
       1:   istore_1        // store int value into variable 1 (i)
       2:   lconst_0        // push the long 0 onto the stack
       3:   lstore_2        // store a long value in a local variable 2 (s2)
       4:   iconst_0        // push the int value 0 onto the stack
       5:   istore  4   // store int value into variable 4 (j)
       7:   lload_2     // load a long value from a local variable 2 (i)
       8:   lconst_1        // push the long 1 onto the stack
       9:   ladd        // add two longs
       10:  lstore_2        // store a long value in a local variable 2 (i)
       11:  iinc    4, 1    // increment local variable 4 (j) by signed byte 1
       14:  iload   4   // load an int value from a local variable 4 (j)
       16:  iload_0     // load an int value from a local variable 0 (L)
       17:  if_icmplt   7   // if value1 is less than value2, branch to instruction at 7
       20:  iinc    1, 1    // increment local variable 1 (i) by signed byte 1
       23:  iload_1     // load an int value from a local variable 1 (i)
       24:  iload_0     // load an int value from a local variable 0 (L)
       25:  if_icmplt   4   // if value1 is less than value2, branch to instruction at 4
       28:  lload_2     // load a long value from a local variable 2 (s2)
       29:  lreturn     // return a long value
    

    Now the bytecode of whileLooP:

       0:   iconst_0        // push int value 0 onto the stack
       1:   istore_1        // store int value into variable 1 (i)
       2:   lconst_0        // push the long 0 onto the stack
       3:   lstore_2        // store a long value in a local variable 2 (s3)
       4:   goto        26
       7:   iconst_0        // push the int value 0 onto the stack
       8:   istore  4   // store int value into variable 4 (j)
       10:  goto        17
       13:  lload_2     // load a long value from a local variable 2 (s3)
       14:  lconst_1        // push the long 1 onto the stack
       15:  ladd        // add two longs
       16:  lstore_2        // store a long value in a local variable 2 (s3)
       17:  iload   4   // load an int value from a local variable 4 (j)
       19:  iinc    4, 1    // increment local variable 4 (j) by signed byte 1
       22:  iload_0     // load an int value from a local variable 0 (L)
       23:  if_icmplt   13  // if value1 is less than value2, branch to instruction at 13
       26:  iload_1     // load an int value from a local variable 1 (i)
       27:  iinc    1, 1    // increment local variable 1 by signed byte 1
       30:  iload_0     // load an int value from a local variable 0 (L)
       31:  if_icmplt   7   // if value1 is less than value2, branch to instruction at 7
       34:  lload_2     // load a long value from a local variable 2 (s3)
       35:  lreturn     // return a long value
    

    To make the output more readable I have append comments describing what each instruction does based on the ‪Java bytecode instruction listings.

    If you take a closer look you will see that there is a important difference between these two bytecodes. The while loop (the same is true for the for loop) has the if statements (if_icmplt instruction) defined at the end of the bytecode. Which means that to check the exit condition of the first loop a goto to line 26 has to be invoked and similarly a goto to line 17 for the second loop.

    The above bytecode was generated using javac 1.6.0_45 on Mac OS X.

    Summary

    I think the different amount of comparisons plus the existence of goto instructions in the while and for loop bytecode is responsible for the performance difference between these loops.

    这篇关于Java循环效率的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆