JDK版本:7u51
scala verson:2.11.2
代码:
阶:
object MyApp extends App{
test
val start = System.nanoTime()
test
println(System.nanoTime() - start)
def test {
var i = 0
while(i < 100000000){
i += 1
}
}
}
的java:
public class MyTest {
public static void main(String[] args) {
test();
long start = System.nanoTime();
test();
System.out.println(System.nanoTime() - start);
}
private static void test(){
int i = 0;
while(i < 100000000){
i += 1;
}
}
}
scala版本需要大约20000000纳秒
java版本需要大约50000纳秒
关于测试的字节码几乎相同但在java中这里是iinc 0 1 [i]
但是在scala中是
而是iload_1 [i]
iconst_1
iadd
istore_1 [i]
(更多字节码)
并且scala中没有JIT优化我删除了第一个测试(预热)它没有明显的效果但是在java中它将花费更多的时间(几乎比以前多100倍,但仍然快10倍左右)比scala版本)
发布后,我尝试过JDK8u25和scala2.11.4,但差别不大。
答案 0 :(得分:3)
iinc 0 1 [i]
这意味着局部变量号0(即[i])增加1。
JVM即时编译器可能将此局部变量存储到CPU寄存器中,因此可以在循环的每个循环中调用INC汇编操作。在任何现代CPU上可能需要一个CPU周期。
iload_1 [i]
iconst_1
iadd
istore_1 [i]
这首先将局部变量编号1(即[i])放在堆栈上。然后将常量1放在堆栈上。然后在堆栈上添加两个顶部元素并将结果放在堆栈顶部。最后一步是将总和复制到局部变量1,即[i]。
即使所有这些操作都是使用CPU的L1缓存和存储在CPU寄存器中的局部变量发生的,它仍然需要比其他情况多得多的周期。
JVM即时编译器可以足够聪明地将其识别为模式并替换为下面的单行。但最初编写它的人不会有这样的动机,因为他们的Java编译器永远不会输出这样的代码。此外,JVM即时编译器具有非常有限的时间预算,可用于所有优化。所以它只能检查有限的优化列表。
这似乎可以通过修复Scala编译器轻松解决。
答案 1 :(得分:3)
不幸的是,目前我还没有简单的方法来测量你的Scala代码,但你的Java test
方法用一个严格的JVM基准测试工具(JMH)测量,时钟在0.5纳秒 - 证明了JIT编译器将其视为无操作。即使我们通过从它返回i
来改进方法的真实性,JIT编译器仍然足够智能,只需用直接公式替换循环,这次的时间仅为1.3纳秒。
从以上几点可以看出:要注意像你所写的那样的微基准。如果在生产环境中将其视为绩效指标,那么从中获得的结果通常会产生误导。
(如果您向我提供带有Scala代码的JAR或.class文件,我也很乐意在JMH上测量该变体。)