也说Java循环优化

最近有不少人提出 Java 循环优化问题,问题分为两类:

 

1

     for ( int i=0; i<10000; i--){ 。。。 }

 

 

     for ( int i = 100000; i > 0; i--){ 。。。 }

 

这个比较无非是 i++ i— 的比较。

 

2

for ( int i=0; i<1000; i++) {

        for ( int j=0; j<100000; j++) {

             。。。

        }

     }

 

 

for ( int i = 0; i < 100000; i++) {

        for ( int j = 0; j < 1000; j++) {

            。。。

        }

     }

 

这个比较主要问题在于循环次数多的放在里面还是外面的问题。

 

我的观点:首先,这种代码上自以为是的优化是没有意义的;其次,拿 C 语言的思维来考虑这个问题表示 Java 基本常识都不懂。

分析:

在具体阐明我的观点前有必要做一点 Java 常识的普及。(注,若没有特别说明,文中 sun 的指的是被 Oracle 收购的那个 sun ,也用来表示 Oracle 接手的 sun 公司的一些产品,如 sun jvm ,既指 Oracle 接手 sun 后的虚拟机,也指未收购时的 sun 的虚拟机)

1)          jvm

众所周知,这是 java 虚拟机。但是,很多人,包括初学者甚至一些工作了的人,对 jvm 的认识仅仅是 sun hotspot 虚拟机,就是从 sun 官网上下载的那个。

而实际上, sun 公司制定的是一个规范,即 Java虚拟机规范 ,搜索 jvmspec 即可得。同时 sun 也提供了该规范的一个标准实现,就是 sun hotspot 虚拟机( N 年前的版本就不说了)。但很多人不知道的是,除了 sun 实现了虚拟机外,还有很多公司也根据自己的需要实现了 Java 虚拟机,比较常见的有 IBM J9 Websphere 中用的), Oracle JRockit Weblogic 中用的), Apache Harmony (由于利益等原因, sun Oracle 都没有给它提供兼容性测试),还有 openJDK 。这些都是比较流行的。诸如此类,还有很多很多。

2)          Java 指令集

jvm 规范中,为 Java 定义了一套指令集。如 iadd iinc 等,指令集用单字节表示,也就是说不超过 255 个。

关于规范中 jvm 指令集最需要注意的一点是:指令集指描述了指令该做什么事情,对于如何去做,是留给 jvm 实现者自己去思考的,所以不同的 jvm 实现对于同一段代码在效率上可能会有很大的差别。譬如,对于 iadd 指令,两个 int 相加,既可以直接交给硬件去做,也可以拐弯抹角的去做,只要最终结果符合 jvm 规范的描述即可。

3)          Java

需要知道的是, Java 考虑到跨平台的需要,所有指令的操作都不是基于寄存器的,而是内存中的 Java 栈。在 C 语言中,有个寄存器用于 pc 计数器,而在 Java 中, pc 计数器是内存中的一个字。 Java 栈由栈帧组成,栈帧分为局部变量区,操作数栈和帧数据区。 jvm 指令的操作数大都源于操作数栈。这与一些语言从寄存器中取操作数是不同的。而局部变量区和操作数栈的大小在编译 Java 文件时就已经确定了。

 

对于问题一,我们有必要看一下 Java 中对于 i++ i— 所使用的指令

public   class Test {

     public   static   void main(String... args) {

        int i = 0;

        i++;

        i--;

     }

}

上面的代码编译后再用 javap –c Test 查看用到的指令:

Compiled from "Test.java"

public class Test extends java.lang.Object{

public Test();

   Code:

    0:    aload_0

    1:    invokespecial    #1; //Methodjava/lang/Object."<init>":()V

    4:    return

 

public static void main(java.lang.String[]);

   Code:

    0:    iconst_0

    1:    istore_1

    2:   iinc     1, 1

    5:   iinc     1, -1

    8:    return

 

}

从上面我们发现 i++ i-- 其实用的是同一个指令,即 iinc ,不过操作数不一样罢了;该指令直接修改局部变量区的值,而不需要压栈。至于如何去实现这个指令,不同的人在实现 jvm 的时候有自己的想法。所以问题一的比较是毫无意义的。你在 jvm 实现 1 上运行很快,可能在 jvm 实现 2 上面就运行很慢。

 

对于问题二,如上面普及常识所言,不同的 jvm 可以有不同的实现,不同的优化。与 C 语言不同之处在于, java 的局部变量区在运行一个方法的时候已经分配好了,不需要遇到一个新变量就去分配。另外一点,因为由 jvm 来执行,这种循环是 jvm 内部是存在优化余地的,譬如,对于里层的循环变量,在重新下一次循环时就没用了,一些 jvm 实现就可以重用这个变量。一些 JVM 可能是纯粹的解释执行字节码,一些 JVM 可能在启动的时候就把字节码编译成 c++ 本地代码,一些 jvm 可能用纯硬件芯片来执行指令集,还有一些 jvm 在运行了一段时间后找出程序热区,仔细优化并将这部分代码编译成 c++ 本地代码来达到最佳效果。所有你能想到的优化都可以在这里做掉。所以不同的 jvm 实现对于这样的代码效率可能会有很大的差别。

经过本人实测(循环体是数值计算), IBM J9 1.6 中两种都很快, Sun hotspot J91.6 相比不是一个数量级的。但是 IBM J9 1.5 的实现则相当的慢,跟 hotspot 比慢的不是一个数量级。有兴趣的可能分别试试 openJDK IBM J9 Jrockit  这些实现的 1.5 1.6 版本的效率,另外一些 jvm 还有 server client 运行版本的区别,效率也是大不一样的

 

总结,隔了一层虚拟机,什么都有可能!

你可能感兴趣的:(java)