代码之家  ›  专栏  ›  技术社区  ›  Andrei Ciobanu

Java循环优化

  •  7
  • Andrei Ciobanu  · 技术社区  · 15 年前

    给出以下(直接)代码:

    public class pr1 {
    
        public static void f1(){
            long sx = 0, s;
            s = System.currentTimeMillis();
            for(long i = 0; i < Integer.MAX_VALUE; ++i){
                sx += i;
            }
            System.out.println("f1(): " + (System.currentTimeMillis() - s));
        }
    
        public static void f2(){
            long sx = 0, s, i;
            s = System.currentTimeMillis();
            i = Integer.MAX_VALUE;
            while(i-->0){
                sx+=i;
            }
            sx += Integer.MAX_VALUE;
            System.out.println("f2(): " + (System.currentTimeMillis() - s));
        }
    
        public static void f3(){
            long sx = 0, s, i;
            s = System.currentTimeMillis();
            i = Integer.MAX_VALUE;
            while(--i>0){
                sx+=i;
            }
            sx += Integer.MAX_VALUE;
            System.out.println("f3(): " + (System.currentTimeMillis() - s));
        }
    
        public static void f4(){
            long sx = 0, s, i;
            s = System.currentTimeMillis();
            i = Integer.MAX_VALUE;
            do{
                sx+=i;
            }while(--i>0);
            System.out.println("f4(): " + (System.currentTimeMillis() - s));
        }
    
        public static void main(String args[]){
            f1();
            f2();
            f3();
            f4();
        }
    }
    

    以及运行代码后的实际结果:

    f1(): 5828
    f2(): 8125
    f3(): 3406
    f4(): 3781
    

    你能给我解释一下大时差吗?理论上,循环实现了相同的功能,但在实践中,似乎四个版本中的每一个都有相关的时间差。

    重复执行之后,结果非常相似。

    后期编辑 作为另一个测试,我重写了主要方法:

    public static void main(String args[]){
        for(int i = 0; i < 4; ++i){
            f1(); f2(); f3(); f4();
        }
    }
    

    新的结果是:

    f1(): 5906
    f2(): 8266
    f3(): 3406
    f4(): 3844
    f1(): 5843
    f2(): 8125
    f3(): 3438
    f4(): 3859
    f1(): 5891
    f2(): 8156
    f3(): 3406
    f4(): 3813
    f1(): 5859
    f2(): 8172
    f3(): 3438
    f4(): 3828
    

    10次代表:

    f1(): 5844
    f2(): 8156
    f3(): 3453
    f4(): 3813
    f1(): 5844
    f2(): 8218
    f3(): 3485
    f4(): 3937
    f1(): 5985
    f2(): 8156
    f3(): 3422
    f4(): 3781
    f1(): 5828
    f2(): 8234
    f3(): 3469
    f4(): 3828
    f1(): 5844
    f2(): 8328
    f3(): 3422
    f4(): 3859
    f1(): 5844
    f2(): 8188
    f3(): 3406
    f4(): 3797
    f1(): 5906
    f2(): 8219
    f3(): 3422
    f4(): 3797
    f1(): 5843
    f2(): 8203
    f3(): 3454
    f4(): 3906
    f1(): 5844
    f2(): 8140
    f3(): 3469
    f4(): 3812
    f1(): 5860
    f2(): 8109
    f3(): 3422
    f4(): 3813
    

    在消除了循环之间的微积分后,结果仍然有点不同:

    public class pr2 {
    
        public static void f1(){
            long sx = 0, s;
            s = System.currentTimeMillis();
            for(long i = 0; i < Integer.MAX_VALUE; ++i);
            System.out.println("f1(): " + (System.currentTimeMillis() - s));
        }
    
        public static void f2(){
            long sx = 0, s, i;
            s = System.currentTimeMillis();
            i = Integer.MAX_VALUE;
            while(i-->0);
            System.out.println("f2(): " + (System.currentTimeMillis() - s));
        }
    
        public static void f3(){
            long sx = 0, s, i;
            s = System.currentTimeMillis();
            i = Integer.MAX_VALUE;
            while(--i>0);
            System.out.println("f3(): " + (System.currentTimeMillis() - s));
        }
    
        public static void f4(){
            long sx = 0, s, i;
            s = System.currentTimeMillis();
            i = Integer.MAX_VALUE;
            do{
            }while(--i>0);
            System.out.println("f4(): " + (System.currentTimeMillis() - s));
        }
    
        public static void main(String args[]){
            for(int i = 0; i < 2; ++i){
                f1(); f2(); f3(); f4();
            }
        }
    }
    

    但时差仍然存在:

    f1(): 3219
    f2(): 4859
    f3(): 2610
    f4(): 3031
    f1(): 3219
    f2(): 4812
    f3(): 2610
    f4(): 3062
    

    JVM:

    java version "1.6.0_20"
    Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
    Java HotSpot(TM) Client VM (build 16.3-b01, mixed mode, sharing)
    

    后期编辑: 对于第一个版本,我使用了javac的-o参数。新的结果是:

    f1(): 3219
    f2(): 4859
    f3(): 2610
    f4(): 3031
    

    后期编辑

    好吧,我在家里尝试过同样的代码,使用Linux机器:

    java version "1.6.0_18"
    OpenJDK Runtime Environment (IcedTea6 1.8) (6b18-1.8-0ubuntu1)
    OpenJDK Server VM (build 14.0-b16, mixed mode)
    

    结果“正常”。现在没问题:

    f1(): 7495
    f2(): 7418
    f3(): 7457
    f4(): 7384
    
    5 回复  |  直到 12 年前
        1
  •  5
  •   Avi    15 年前

    当我在JVM(Java热点(TM)64位服务器VM(构建16.0 B13,混合模式))上运行此代码时,所有四个函数都给出了类似的结果:

    f1(): 3234
    f2(): 3132
    f3(): 3114
    f4(): 3089
    

    我想您的JVM在某个地方没有进行相同的优化。

    您可以使用javap检查为不同函数生成的字节码: javap -l -c pr1 . 当我这样做的时候,我得到了下面的f2():

    public static void f2();
      Code:
       0:   lconst_0
       1:   lstore_0
       2:   invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
       5:   lstore_2
       6:   ldc2_w  #3; //long 2147483647l
       9:   lstore  4
       11:  lload   4
       13:  dup2
       14:  lconst_1
       15:  lsub
       16:  lstore  4
       18:  lconst_0
       19:  lcmp
       20:  ifle    31
       23:  lload_0
       24:  lload   4
       26:  ladd
       27:  lstore_0
       28:  goto    11
       31:  lload_0
       32:  ldc2_w  #3; //long 2147483647l
       35:  ladd
       36:  lstore_0
       37:  getstatic       #5; //Field java/lang/System.out:Ljava/io/PrintStream;
       40:  new     #6; //class java/lang/StringBuilder
       43:  dup
       44:  invokespecial   #7; //Method java/lang/StringBuilder."<init>":()V
       47:  ldc     #13; //String f2():
       49:  invokevirtual   #9; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
       52:  invokestatic    #2; //Method java/lang/System.currentTimeMillis:()J
       55:  lload_2
       56:  lsub
       57:  invokevirtual   #10; //Method java/lang/StringBuilder.append:(J)Ljava/lang/StringBuilder;
       60:  invokevirtual   #11; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
       63:  invokevirtual   #12; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
       66:  return
    

    F2()变慢的一个可能原因是编译器/JVM对 while(i-->0) 后减量运算符。基本上,你需要 i 无论是在增量之前还是之后,所以如果简单地实现了这个操作,就需要做更多的工作。

        2
  •  10
  •   Community CDub    8 年前

    实际上,您是在基准测试JVM,而不是代码。

    参见:


    更新 :好的,回答有点简明扼要。使用postfix运算符的循环( i-- )似乎比使用前缀运算符的循环慢( --i )这可能是真的,因为值在表达式的计算过程中发生了更改,但编译器需要保存一个要在表达式中使用的原始值的副本。使用前缀运算符可以避免保留副本,因为表达式中将只使用更改后的值。

    参见:

    毕竟,这种微优化可以在2 三十一 处决。你真的经常执行吗?比起过早的优化,我更喜欢可读性。

        3
  •  1
  •   seand    15 年前

    多次运行后,热点编译器可能会优化每个方法。这需要时间和变化。但是,由于所有的循环都是以同样的方式工作的,因此,时代最终会变得相似似乎是合理的。

        4
  •  1
  •   Stephen C    15 年前

    我怀疑这与在32位ALU的机器上执行64位算术有关。我怀疑,在本机指令级别上,由于细微的流水线效应,增量/减量之前/之后的某些测试组合需要更长的时间。事实上,有人报告说这些数字是平放在64位机器上的,这支持了这一理论。确认这一点的方法是获取由JIT编译器生成的本机代码的转储,获取特定CPU的文档并找出时钟周期的去向。

    但老实说,我不知道这是否值得。我们有明确的证据表明,您的微基准测试数字依赖于CPU,并且所做的“工作”显然不具有代表性。(你为什么要用 long 32位机器上的循环计数器?)

    我也有点惊讶于JIT编译器没有发现循环(在每种情况下)可以完全优化掉。

        5
  •  1
  •   Redlab    15 年前

    执行速度不同的原因

    • JVM的垃圾收集可以 在某些执行过程中运行
    • 您的操作系统计划其他 要执行的任务,因此也为其他应用程序提供了资源。甚至优先于他们 然后可能由于编译do/while和for的方式有一些不同,但是这些应该被忽略。

    让我们把我的答案划掉。

    这肯定是由于OS或Java为Windows编译的方式,我在Windows上测试过,并在Windows上获得了像您这样的结果。