使用Java对Scala进行基准测试

最近在工作中出现了一个有关Java和Scala之间的基准测试的问题。 也许您碰到我的博客文章是因为您也想知道Java或Scala哪个更快。 好吧,我很抱歉这么说,但是如果那是你,你在问一个错误的问题。 在本文中,我将向您展示Scala比Java更快。 之后,我将向您展示为什么这个问题是一个错误的问题以及为什么我的结果应该被忽略。 然后,我将解释您应该问什么问题。

基准

今天,我们将选择一种非常简单的基准算法,即快速排序算法。 我将提供Scala和Java的实现。 然后,我将分别对100000个元素的列表进行100次排序,并查看每个实现对它进行排序所花费的时间。 因此,让我们从Java开始:

public static void quickSort(int[] array, int left, int right) {
        if (right <= left) {
            return;
        }
        int pivot = array[right];
        int p = left;
        int i = left;
        while (i < right) {
            if (array[i] < pivot) {
                if (p != i) {
                    int tmp = array[p];
                    array[p] = array[i];
                    array[i] = tmp;
                }
                p += 1;
            }
            i += 1;
        }
        array[right] = array[p];
        array[p] = pivot;
        quickSort(array, left, p - 1);
        quickSort(array, p + 1, right);
    }

为此,我在配备Retina显示屏的2012 MacBook Pro上对100000个元素的列表进行了100次排序,耗时85​​2ms。 现在,Scala实现:

def sortArray(array: Array[Int], left: Int, right: Int) {
    if (right <= left) {
      return
    }
    val pivot = array(right)
    var p = left
    var i = left
    while (i < right) {
      if (array(i) < pivot) {
        if (p != i) {
          val tmp = array(p)
          array(p) = array(i)
          array(i) = tmp
        }
        p += 1
      }
      i += 1
    }
    array(right) = array(p)
    array(p) = pivot
    sortArray(array, left, p - 1)
    sortArray(array, p + 1, right)
  }

它看起来与Java实现非常相似,但语法略有不同,但通常是相同的。 和时间相同的基准? 695毫秒。 没有图形,没有基准是完整的,因此让我们看一下外观:

所以你有它。 Scala比Java快20%。 QED等。

错误的问题

但是,这还不是全部。 没有微基准测试。 因此,让我们从回答为什么Scala在这种情况下比Java更快的问题开始。 现在,Scala和Java都在JVM上运行。 他们的源代码都编译为字节码,从JVM的角度来看,它不知道一个是Scala还是一个是Java,它只是JVM的所有字节码。 如果我们看一下上面已编译的Scala和Java代码的字节码,我们会注意到一个关键的事情,在Java代码中, quickSort例程有两个递归调用,而在Scala中,只有一个。 为什么是这样? Scala编译器支持一种称为尾调用递归的优化,其中,如果方法中的最后一条语句是递归调用,则它可以摆脱该调用并将其替换为迭代解决方案。 这就是为什么Scala代码比Java代码快得多的原因,这就是这种尾调用递归优化。 您可以在编译Scala代码时关闭此优化,当我这样做时,它现在需要827毫秒,仍然快一点,但速度不高。 我不知道为什么Scala在没有尾调用递归的情况下仍然更快。

除了像这样的一些额外的利基优化之外,这使我想到了下一个要点,Scala和Java都可以编译为字节码,因此对于可比较的代码具有几乎相同的性能特征。 实际上,在编写Scala代码时,您倾向于在Java和Scala之间使用很多完全相同的库,因为对于JVM来说,它们只是字节码。 这就是为什么对照Java对Scala进行基准测试是错误的问题。

但这还不是全部。 我在Scala中实现的快速排序并不是我们所谓的惯用Scala代码。 它以强制性方式实施,非常注重性能-应该是用于性能基准测试的代码。 但这并不是Scala开发人员每天都会写的风格。 这是一种惯用的Scala风格的快速排序实现:

def sortList(list: List[Int]): List[Int] = list match {
    case Nil => Nil
    case head :: tail => sortList(tail.filter(_ < head)) ::: head :: sortList(tail.filter(_ >= head))
  }

如果您不熟悉Scala,那么这段代码乍看之下似乎不胜枚举,但是请相信我,在学习了该语言几周之后,您会完全自如地阅读它,并且会发现它比Java语言更清晰,更易于维护。先前的解决方案。 那么这段代码如何执行? 答案是非常糟糕的,它花费了13951ms,比其他Scala代码长20倍。 强制性图表:

所以我是说,当您以“正常”方式编写Scala时,您的代码性能将始终很糟糕? 嗯,这并不是Scala开发人员一直都在编写代码的方式 ,他们不是傻瓜,他们知道代码的性能后果。

要记住的关键是,开发人员解决的大多数问题都不是快速排序,它们不是计算繁重的问题。 例如,一个典型的Web应用程序关心的是移动数据,而不是执行复杂的算法。 Web开发人员为处理Web请求可能编写的一段Java代码的计算量可能要花费整个运行请求的1微秒时间,即一百万分之一秒。 如果等效的Scala代码花费20微秒,那仍然只有十分之一秒。 整个请求可能需要20毫秒来处理,包括访问数据库几次。 因此,使用惯用的Scala代码将使响应时间增加0.1%,这几乎没有。

因此,Scala开发人员在编写代码时将以惯用的方式编写代码。 正如您在上面看到的,惯用的方法是简洁明了。 它易于维护,比Java容易得多。 但是,当他们遇到他们知道在计算上很昂贵的问题时,他们将恢复为更像Java的样式进行编写。 这样,他们就拥有了两全其美的优势,大部分代码库都易于维护惯用的Scala代码,而性能至关重要的性能良好的Java类代码。

正确的问题

那么,在性能方面将Scala与Java进行比较时,您应该问什么问题? 答案是以Scala的名义。 斯卡拉建是一个“ 斯卡拉 BLE LA nguage”。 正如我们已经看到的那样,这种可扩展性在微型基准测试中并不存在。 那么它到底在哪里呢? 这将是我撰写的未来博客文章的主题,在该博客文章中,我将展示一些更接近于Scala Web应用程序与Java Web应用程序的真实世界基准,但是为了给您一个想法,答案来自于Scala如何Scala生态系统提供的语法和库非常适合编写可伸缩的容错系统所需的编程范例。 确切的等效字节码可以用Java实现,但是这将是一个巨大的噩梦,无法遵循匿名内部类,因为始终担心会意外地改变错误的共享状态,并且存在大量竞争条件和内存可见性问题。

简而言之,您应该问的问题是:当服务器因意外负载而倒下时,Scala将如何帮助我? 这是一个现实世界中的问题,我相信任何具有任何现实世界经验的IT专业人员都愿意回答。 请继续关注我的下一篇博客文章。

参考: James and Beth Roper的博客博客中的JCG合作伙伴 James Roper 对Java进行了Scala基准测试

翻译自: https://www.javacodegeeks.com/2012/11/benchmarking-scala-against-java.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值