大多数程序员都认为C/C++会比Java语言快,甚至觉得从Java语言诞生以来“执行速度缓慢”的帽子就应当扣在它的头顶,这种观点的出现是由于Java刚出现的时候即时编译技术还不成熟,主要靠解释器执行的Java语言性能确实比较低下。但目前即时编译技术已经十分成熟,Java语言有可能在速度上与C/C++一争高下吗?要想知道这个问题的答案,就让我们从两者的编译器谈起。
Java与C/C++的编译器对比实际上代表了最经典的即时编译器与静态编译器的对比,很大程度上也决定了Java与C/C++性能对比的结果,因为无论是C/C++还是java代码,最终编译之后被机器执行的都是本地机器码,哪种语言性能更高,出来她们自身的API库实现得好坏以外,其余比较就成了异常“拼编译器”和“拼输出代码质量”的游戏。当然这种比较也是剔除了开发效率的片面对比,语言间孰优孰劣,谁快谁慢的问题都是很难有结果的争论,下面我们就回到正题,看看这两种语言的编译器各有何种优势。
Java虚拟机的即时编译器与C/C++的静态编译器相比,可能会由于下列这些原因而导致输出的本地代码有一些略势(下面列举的也包括一些虚拟机执行子系统的性能劣势):
1.因为即时编译器运行占用的是用户程序的运行时间,具有很大的时间压力,它能提供的优化手段也受制于编译成本。如果编译速度不能达到要求,那用户将在启动程序或程序的某部分察觉到重大延迟,这点使得即时编译器不敢随便引入大规模的优化技术,而编译时间成本在静态优化编译器中并不是主要的关注点。
2.Java语言是动态类型安全语言,这就意味着需要由虚拟机来确保程序不会违反语言语义或访问非结构化内存。从实现层面上看,这就意味着虚拟机必须频繁地进行动态检查,如实例方法访问时检查空指针,数组元素访问时检查上下界范围,类型转换时检查继承关系等。对于这类程序代码没有明确写出的检查行为,尽管编译器会努力进行优化,但总体上仍然要消耗不少的运行时间。
3.Java语言中虽然没有virtual关键字,但是使用虚方法的频率却远远大于C/C++语言,这意味着运行时对方法接收者进行多态选择的频率要远远大于C/C++语言,也意味着即时编译器在进行一些优化(如前面提到的方法内联)时难度要远大于C/C++的静态优化编译器。
4.Java语言是可以动态扩展的语言,运行时加载新的类可能改变程序类型的继承关系,这使得很多全局的优化都难以进行,因为编译器无法看见程序的全貌,许多全局的优化措施都只能以激进优化的方式来完成,编译器不得不时刻注意并随着类型的变化而在运行时撤销或重新进行一些优化。
5.Java语言中对象的内存分配都是在堆上进行的,只有方法中的局部变量才能在栈上分配。而C/C++的对象则有多种内存分配方式,既可能在堆上分配,又可能在栈上分配,如果可以再栈上分配线程私有的对象,将减轻内存回收的压力。另外,C/C++中主要由用户程序代码来回收分配的内存,这就不存在无用对象的筛选过程,因此效率上(仅指运行效率,排除了开发效率)也比垃圾收集器要高。
上面说了一大堆Java语言与C/C++的劣势,不是说Java就真的不如C/C++了,相信读者也注意到了,Java语言的这些性能上的劣势都是为了换区开发效率上的优势而付出的代价,动态安全,动态扩展,垃圾回收这些拖后腿的特性都为Java语言的开发效率做出了很大贡献。何况,还有许多优化是Java的即时编译器能做而C/C++的静态编译器补鞥呢做或者不好做的。例如:在C/C++中,别名分析的难度就要远高于Java。Java的类型安全保证了在类似如下代码中,只要ClassA和ClassB没有继承关系,那对象ObjA和ObjB就绝不可能是同一个对象,即不会是同一块内存两个不同的别名。
public void foo(ClassA objA,ClassB objB){
objA.x = 123;
objB.y = 456;
//只要objB.y不是objA的别名,下面就可以保证输出为123
System.out.println(objA.x);
}
确定了objA和objB并非对方的别名后,许多与数据依赖相关的优化才可以进行(重排序,变量代换)。具体到这个例子中,就是无须担心objB.y其实与objA.x指向同一块内存,这样就可以安全地确定打印语句中的objA.x为123。
Java编译器另外一个红利是由它的动态性所带来的,由于C/C++编译器所有优化都在编译期完成,以运行期性能监控为基础的优化措施它都无法进行,如调用频率预测,分子频率预测,剪裁未被选择的分支等,这些都成为java语言独有的性能优势。
作者:ExceptionMapping