为什么在scala中的位置比java慢近400倍?

时间:2014-12-10 17:26:02

标签: java scala

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,但差别不大。

2 个答案:

答案 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上测量该变体。)