Scala 性能:为啥这个 Scala 应用程序比同等的 Java 应用程序慢 30 倍?

Posted

技术标签:

【中文标题】Scala 性能:为啥这个 Scala 应用程序比同等的 Java 应用程序慢 30 倍?【英文标题】:Scala perf: Why is this Scala app 30x slower than the equivalent Java app?Scala 性能:为什么这个 Scala 应用程序比同等的 Java 应用程序慢 30 倍? 【发布时间】:2012-04-05 15:29:01 【问题描述】:

我是一名非常精通 C# 的开发人员,但需要开始编写可在 JVM 上运行的代码。如今,Java 语言与 C# 相比功能较差,因此我对 Scala 提供的功能很感兴趣。

但是,当听说在 Scala 中,所有运算符都是简单的方法时,我开始怀疑这会对大量数学计算产生的性能影响(这对于我的团队编写的应用程序类型很重要)

所以我运行了一些简单的基于 int 的测试,发现 Scala 比等效的 Java 代码慢了大约 30 倍。不好!谁能告诉我我做错了什么?或者如何将scala示例的计算性能提高到与Java相当?

UPDATE1:正如前两个答案所指出的,我是一个超级菜鸟,并在 IntelliJ IDE 中运行它。我不知道如何通过 java 命令行运行 scala 应用程序,这可能是 IntelliJ 问题。感谢各位的帮助,在继续进行性能测试之前,我需要调查 scala 的简单命令行执行,因为 IDE 给出的结果显然太不准确了。

UPDATE2:cmets 中的 Luigi 在 IntelliJ 中说他得到了相等的时间,所以看来我的巨大差异不是由于 IntelliJ 造成的吗?关于这可能是什么的任何其他想法?我将尝试通过命令行运行它并发布我的结果更新。

更新3: 通过命令行运行后,我得到了相同的 30 倍性能差异。 我的电脑是 3 核 AMD x64 3.4Ghz,运行 J2SE 6 jdk 64bit 1.6.0_31,Window7。

这是我的运行时: Java:210 毫秒。 Scala:在 2000 到 7400ms 之间(一般是 7000 范围)

所以,我想这个问题仍然悬而未决。为什么 scala 在我的平台上运行如此缓慢?使用 java 64 位运行时,还是使用 Java 6?

运行时版本:

C:\Users\jason>java -showversion
java version "1.6.0_31"
Java(TM) SE Runtime Environment (build 1.6.0_31-b05)
Java HotSpot(TM) 64-Bit Server VM (build 20.6-b01, mixed mode)

C:\Users\jason>scala
Welcome to Scala version 2.9.1-1 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_31).

更新 4 虽然我的原始测试有 30 倍的差异,但将迭代次数增加到 100000000 会导致差异缩小到大约 33%,所以似乎 scala 仍然受到我机器上一些未知初始化成本的支配。我将用评分最高的答案结束这个问题,因为我认为我们不会发现性能问题,因为除了我之外没有人看到这个问题:(

*更新 5,解决方案:根据我得到的 2 个答案的帮助,我发现了问题,请参阅下面的答案了解更多详细信息(总结:第一次调用 System.nanoTime() 需要很长一段时间)*

这是我的示例应用:

//scala
object HelloWorld 
  //extends Application 
  def main(args: Array[String]) 
    println("hello scala")
    var total: Long = 0
    var i: Long = 0
    var x: Long=0;
    //warm up of the JVM to avoid timing of runtime initialization
    while (i < 100000)
    
      x=i;
      x += x - 1;
      x -= x + 1;
      x += 1;
      x -= 1;
      total += x;
      i+=1;
    
    //reset variables
    total = 0
    i = 0;
    //start timing
    var start: Long = System.nanoTime

    //run test
    while (i < 100000) 
      x=i;
      x += x - 1;
      x -= x + 1;
      x += 1;
      x -= 1;

      total += x;
      i+=1;
    
    var end: Long = System.nanoTime
    System.out.println("ms, checksum = ")
    System.out.println((end - start) / 1000)
    System.out.println(total)
  

这是 java 的等价物,快 30 倍

//java
public class app 
    public static void main(String[] args)
    
        String message = "hello, java";
        System.out.println(message);
        long total = 0;
        //warm up of the JVM to avoid timing of runtime initialization
        for(long i=0;i< 100000;i++)
        
            long x=i;
            x+=x-1;
            x-=x+1;
            x++;
            x--;
            total+=x;
        
        //reset variables
        total = 0;
        //start timing and run test
        long start = System.nanoTime();
        for(long i=0;i< 100000;i++)
        
            long x=i;
            x+=x-1;
            x-=x+1;
            x++;
            x--;
            total+=x;
        
        long end = System.nanoTime();
        System.out.println("ms, checksum = ");
        System.out.println((end-start)/1000);
        System.out.println(total);
    

【问题讨论】:

从答案来看,我运行测试的方式可能存在问题。我正在使用 IntelliJ 11.1 来运行这两个测试。我不知道如何从命令行运行 Scala 应用程序(我可以很好地运行 java 应用程序),所以这就是我在 IDE 中运行它的原因。谁能告诉我如何通过 java.exe (windows) jvm 执行 Scala 应用程序?或者,如果有办法从 IntelliJ 的 Scala 测试中获得更好的性能,我该怎么做?我是一名 .NET 开发人员,所以我不熟悉如何正确执行此基准测试。非常感谢您的帮助。 从 IntelliJ 11.0 运行的两个版本的时间完全相同 .... 那么,如果有人对问题所在有任何想法,我很想听听,因为我得到 java 208ms 和 scala 大约 7000ms :( 您是否使用相同的 JVM 版本来运行两者? 64 位 / 32 位和 Java 7 vs 6 可以产生很大的不同。 对不起 Luigi,我不够熟练,不知道如何确定我是否安装了 32 位运行时,或者 java 或 scala 版本是否正在使用它。我安装了 J2SE 6 SDK(64 位),这是我用于开发的,所以我假设这就是 intellij 用于两者的。 【参考方案1】:

所以,我想我自己想出了答案。

问题在于对System.nanoTime 的调用。这样做会产生一些初始化成本(加载 Java 基础库等),从 Java 运行时调用时加载成本要比从 Scala 运行时低得多。

我通过更改 total 的初始值来证明这一点,而不是将其设置为

var total: Long = System.nanoTime()

这是在第一个“预热”循环之前添加的,现在这样做会使应用程序的两个版本(Java 和 Scala)同时运行:1000000 次迭代大约需要 2100。

感谢你们的帮助,如果没有你们的帮助,我不会想到这一点。

ps:我将保留“已接受的答案”,因为如果没有他的帮助,我不会追查到这一点。

【讨论】:

【参考方案2】:

我重新运行了您的代码(并增加了 x1000 的周期数,以便在基准测试中获得一些意义)。

结果:

Scala: 92 ms
Java: 59 ms

您可以看到 Java 速度提高了 30%。

看字节码,我可以说两个版本几乎一模一样——所以区别真的很奇怪(字节码列表很长,这里就不贴了)。

将计数增加 x10000 得到:

Scala: 884 ms
Java: 588 ms

由于结果相当稳定,因此应该有一些常数因素潜伏在某处。也许在“scala”运行器传递给 JVM 的某些参数中?

编辑:

我的配置:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

$ scala -version
Scala code runner version 2.9.0.1 -- Copyright 2002-2011, LAMP/EPFL

$ inxi -SCD
System:    Host the-big-maker Kernel 2.6.35-22-generic x86_64 (64 bit) Distro Linux Mint 10 Julia
CPU:       Quad core AMD Phenom II X4 965 (-MCP-) cache 2048 KB flags (lm nx sse sse2 sse3 sse4a svm) 
           Clock Speeds: (1) 800.00 MHz (2) 800.00 MHz (3) 800.00 MHz (4) 800.00 MHz
Disks:     HDD Total Size: 750.2GB (5.8% used) 1: /dev/sda OCZ 90.0GB 
           2: /dev/sdb ST3500413AS 500.1GB 3: /dev/sdc ST3802110A 80.0GB 
           4: /dev/sdd Maxtor_6Y080M0 80.0GB 

【讨论】:

绝对没有我看到的那么糟糕。 2 倍性能差异在可接受的范围内。你的回答加上下一个告诉我,我需要找到一种更好的方法来执行 Scala 代码来进行适当的基准测试。 感谢您提供有关字节码的额外信息,因为这正是我所关心的。你和n.m.给出了及时和好的答案,但你是第一个! @jaysun - 一点建议 - 关于“过早优化”的旧建议。请记住,Scala 最擅长编写正确、类型安全和惯用的代码——而不是高性能代码(所有那些装箱和隐式......)。而且,如果你真的需要从代码中榨取最佳性能,你可以随时使用 java 甚至纯字节码(没有任何限制——Scala 与 Java 具有出色的互操作性)。 @jaysun - scalac 尝试在编译期间对原语的所有这些操作进行拆箱,因此大多数时候它们编译为相同的代码:)。大约 30 倍 - 也许您的配置有问题?例如,Scala 和 Java 的版本可能很有趣。 @Rogach 对我来说字节码是一样的,结果也是一样的。即使我将迭代次数提高到 1e8。【参考方案3】:
$ javac app.java
$ scalac app.scala 
$ scala HelloWorld
hello scala
ms, checksum = 
1051
-100000
$ java app
hello, java
ms, checksum = 
1044
-100000

我做错了什么?

【讨论】:

那时我可能是无知的。我试图在 IDE 中运行,但不知道如何通过 java.exe 命令行运行 scala 应用程序。我会尝试通过 scala 运行时运行它,看看是否更好。

以上是关于Scala 性能:为啥这个 Scala 应用程序比同等的 Java 应用程序慢 30 倍?的主要内容,如果未能解决你的问题,请参考以下文章

为啥在这个 Scala 代码中需要向上转换?

Scala 中不可变集实现的性能

为啥 Scala 抱怨类型不匹配?

为啥 Scala 偶尔会退回到 Java 对象?

Scala 列表:为啥 IDEA 提示未使用的表达式没有副作用

为啥单例对象创建的scala程序不需要静态main方法?