Scala vs Java,性能和内存?[关闭]


160

我热衷于研究Scala,并提出了一个似乎无法找到答案的基本问题:一般来说,Scala和Java在性能和内存使用方面是否有所不同?


3
我听说性能可能非常接近。我怀疑这很大程度上取决于您的工作。(因为Java与C相对应)
彼得·劳里

这类问题的答案是“取决于”的-实际上是对系统X与系统Y的任何比较。此外,这是stackoverflow.com/questions/2479819/…
James Moore

Answers:


261

Scala使得无需意识到即可轻松使用大量内存。这通常非常强大,但有时可能很烦人。例如,假设您有一个字符串数组(称为array),以及从这些字符串到文件的映射(称为mapping)。假设您要获取映射中所有来自长度大于两个的字符串的文件。在Java中,您可能

int n = 0;
for (String s: array) {
  if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
  if (s.length <= 2) continue;
  bigEnough[n++] = map.get(s);
}

ew!辛苦了 在Scala中,执行相同操作的最紧凑的方法是:

val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)

简单!但是,除非你非常熟悉的藏品是如何工作的,你可能不知道的是,这样做会创建一个额外的中间阵列(这种方式filter),和一个额外的对象数组的每一个元素(有mapping.get,它返回一个选项)。它还创建了两个函数对象(一个用于过滤器,一个用于flatMap),尽管由于函数对象很小,所以这很少成为主要问题。

因此,基本上,内存使用量在原始级别上是相同的。但是Scala的库具有许多强大的方法,可让您轻松地创建大量(通常是短暂的)对象。垃圾收集器通常可以很好地处理这种垃圾,但是如果您完全不了解正在使用的内存,那么Scala可能会比Java更快地遇到麻烦。

请注意,计算机语言基准游戏Scala代码以类似Java的风格编写,以获得类似Java的性能,因此具有类似Java的内存使用量。您可以在Scala中做到这一点:如果您编写的代码看起来像高性能的Java代码,那么它将是高性能的Scala代码。(您也许可以用更惯用的Scala风格编写它,但仍然可以获得良好的性能,但这取决于具体情况。)

我应该补充一点,每花时间进行编程,我的Scala代码通常比Java代码更快,因为在Scala中,我可以花更少的精力完成乏味的非性能关键部分,并花更多的精力优化算法和性能关键部件的代码。


172
最后一段+1。这是剩下的考虑了一个重要的点过于频繁。
凯文·赖特

2
我认为,意见可以对您提到的问题大有帮助。还是不是特别针对数组?
Nicolas Payette

1
@凯文·赖特(Kevin Wright)-“这是一个经常被忽略的重要问题”-这很容易讲,很难证明,并且告诉我们有关雷克斯·克尔(Rex Kerr)技能的一些知识,而不是其他技能较弱的人所不能达到的。
igouy 2011年

1
>>具有最高级表现的惯用风格<<在基准游戏中,尚有无法实现“最高级”表现的惯用Scala程序的空间。
igouy 2011年

2
@RexKerr-您的Java示例是否没有为每个可能的字符串查找映射键两次,而您的Scala示例仅在选择了字符串之后才查找一次?是否针对不同的数据集以不同的方式对它们进行了优化?
塞斯

103

我是新用户,所以我无法在上面的Rex Kerr答案中添加评论(顺便说一句,允许新用户“回答”而不是“评论”)。

我注册仅是为了回应Rex在上面流行的回答“ Java,它是如此冗长而艰苦”。当然,您可以编写更简洁的Scala代码,但给出的Java示例显然很肿。大多数Java开发人员都会编写如下代码:

List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
  if(s.length() > 2 && mapping.get(s) != null) {
    bigEnough.add(mapping.get(s));
  }
}

当然,如果我们要假装Eclipse不能为您完成大部分实际键入操作,并且每个保存的字符确实使您成为一个更好的程序员,那么您可以编写以下代码:

List b=new ArrayList();
for(String s:array)
  if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));

现在,我不仅节省了输入完整的变量名和花括号的时间(使我有5秒钟的时间来思考更深的算法思想),而且我还可以在混淆竞赛中输入代码,并有可能赚取额外的现金假期。


7
您为何不加入“本月流行语言”俱乐部?很好的评论。我特别喜欢阅读最后一段。
stepanian '04

21
精湛的放!我厌倦了虚构的示例,在这些示例中,充实的Java代码之后是一些精心构造的Scala(或某些其他FP语言)简洁的示例,然后仓促得出结论,因为它,Scala必须比Java更好。谁曾经在Scala中写过任何重要的东西!;-)不要说Twitter ...
chrisjleu 2012年

2
好吧,Rex的解决方案为数组预分配了内存,这将使编译后的代码运行得更快(由于您的方法,您让JVM随着数组的增长而定期对其进行重新分配)。即使涉及更多类型的输入,从性能角度来看它也可能是赢家。
Ashalynd 2015年

5
而在Java8中,它将是:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl

2
使Scala在某些方面比Java更“出色”的是扩展的类型系统功能,该功能使表达类型更通用的模式(例如Monad,Functor等)变得更加容易。这样,您就可以创建由于过于严格的合同而不会妨碍您的类型,这在Java中经常发生。严格的合同(不基于代码中的实际模式)是仅为了正确地对代码进行单元测试而必须进行“责任倒置”模式的原因(首先考虑了依赖注入及其带来的XML Hell)。地址。简洁带来的灵活性仅仅是一种奖励。
josiah

67

像Java一样编写Scala,您可以期望发出几乎相同的字节码-度量值几乎相同。

用不可变的对象和高阶函数更“惯用地”编写它,它会变慢一些并变大一些。这种经验法则的一个例外是,当使用类型参数使用@specialised注释的泛型对象时,这将创建更大的字节码,从而避免装箱/拆箱,从而超过Java的性能。

还值得一提的是,在编写可以并行运行的代码时,不可避免的是要增加内存/降低速度。与典型的Java代码相比,惯用的Scala代码本质上更具声明性,并且与.par完全并行的代码通常仅相差4个字符()。

因此,如果

  • 在单个线程中,Scala代码比Java代码长1.25倍
  • 它可以轻松拆分为4个内核(即使在笔记本电脑中也常见)
  • 并行运行时间为(1.24 / 4 =)0.3125倍于原始Java

您是否会说现在Scala代码要慢25%或快3倍?

正确答案取决于您如何定义“性能” :)


4
顺便提一句,您可能要提到.par2.9中的内容。
雷克斯·克尔

26
>>然后您能说Scala代码现在慢25%或快3倍吗?<<我想说为什么您的假设与多线程Java代码不做比较?
igouy 2011年

17
@igouy-关键是说的假设代码不存在,“更快”的Java代码的命令性使其很难并行化,因此成本/收益比意味着它根本不可能发生。另一方面,习惯用法Scala本质上更具声明性,可以经常并发,而无非是琐碎的更改。
凯文·赖特

7
并发Java程序的存在并不意味着典型的 Java程序可以轻松地适应并发。如果有的话,我想说特殊的fork-join样式在Java中特别少见,必须进行显式编码,而简单的操作(例如查找最小包含值或集合中的值之和)可以并行进行在Scala中,只需使用即可.par
凯文·赖特

5
不,我可能不会。这种事情是许多算法的基本构建块,并且要在语言和标准库(所有程序都将使用相同的标准库,而不仅仅是典型的库)中以较低的级别显示它,这证明了您只需选择语言,就已经接近并发了。例如,在集合上进行映射本质上适合于并行化,并且不使用该map方法的Scala程序的数量将很少。
凯文·赖特

31

计算机语言基准游戏:

速度测试 Java / scala 1.71 / 2.25

内存测试 Java / scala 66.55 / 80.81

因此,该基准测试表明Java速度提高了24%,scala使用的内存增加了21%。

总而言之,这没什么大不了的,在现实世界中的应用程序中也没关系,在现实世界中,大多数时间都由数据库和网络消耗。

底线:如果Scala使您和您的团队(以及您离开时接管项目的人们)更具生产力,那么您应该坚持下去。


34
代码大小 java / scala 3.39 / 2.21
hammar,2011年

22
注意这些数字,它们听起来非常精确,而实际上它们几乎没有任何意义。似乎Scala的平均速度始终不比Java快24%,等等
。– Jesper

3
Afaik引用的数字表明情况恰恰相反:Java比scala快24%。但是正如您所说-它们是微基准,不需要与实际应用中发生的情况匹配。而且,使用不同语言的不同方式或问题解决方案最终可能导致程序的可比性较差。
用户未知,

9
“如果Scala让您和您的团队...”底线:您会在不久之后知道的:-)
igouy 2011年

基准测试游戏的“帮助”页面提供了有关如何“比较两种语言实现的程序速度和大小”的示例。对于Scala和Java的适当的比较网页是- shootout.alioth.debian.org/u64q/scala.php
igouy

20

其他人已经就紧密循环回答了这个问题,尽管我评论过的Rex Kerr的示例之间似乎存在明显的性能差异。

这个答案的确是针对那些可能研究将紧环优化作为设计缺陷的人。

我对Scala还是比较陌生的(大约一年左右),但是到目前为止,它的感觉是它使您可以相对轻松地推迟设计,实现和执行的许多方面(具有足够的背景知识和实验:)

延期设计功能:

延迟的实现功能:

延迟执行功能:(对不起,没有链接)

  • 线程安全的惰性值
  • 传递名字
  • 单子的东西

对我来说,这些功能是帮助我们踏上快速,紧凑应用程序之路的那些功能。


雷克斯·克尔(Rex Kerr)的示例在执行的哪些方面被推迟方面有所不同。在Java示例中,将内存分配推迟到计算出内存大小为止,而在Scala示例中,内存分配推迟了映射查找。在我看来,它们似乎是完全不同的算法。

在我的Java示例中,我认为这更多的是苹果对苹果的等效:

val bigEnough = array.collect({
    case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})

没有中介的集合,没有Option实例等,这也保留了集合类型,这样bigEnough的类型Array[File]- Arraycollect实施可能会做沿着什么克尔先生的Java代码做线的东西。

我上面列出的延迟设计功能还将使Scala的collection API开发人员可以在将来的版本中实现特定于Array的快速collect实现,而不会破坏API。我指的是迈向速度之路。

也:

val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)

withFilter我在这里使用的方法不是filter解决中间集合问题,但仍然存在Option实例问题。


Scala中简单执行速度的一个示例是日志记录。

在Java中,我们可能会这样写:

if (logger.isDebugEnabled())
    logger.debug("trace");

在Scala中,这只是:

logger.debug("trace")

因为要在Scala中调试的消息参数的类型为“ => String”,我认为它是一种无参数函数,该函数在求值时执行,但文档称其为传递名称。

编辑{Scala中的函数是对象,因此这里有一个额外的对象。在我的工作中,琐碎对象的重量值得消除不必要评估日志消息的可能性。}

这并不能使代码更快,但确实可以使代码更快,并且我们不太可能具有遍历和清理其他人代码的经验。

对我来说,这是Scala中一个一致的主题。


硬代码无法说明为什么Scala更快,尽管确实暗示了一点。

我觉得这是代码重用和Scala中代码质量上限的结合。

在Java中,出色的代码通常被迫变成难以理解的混乱,因此在生产质量的API中实际上并不可行,因为大多数程序员都无法使用它。

我非常希望Scala可以让我们当中的爱因斯坦实现更强大的API,这些API可能通过DSL来表达。Scala中的核心API已经走了这条路。


您的日志记录工作是Scala性能陷阱的一个很好的例子:logger.debug(“ trace”)为无参数函数创建了一个新对象。
jcsahnwaldt恢复莫妮卡2012年

确实-这如何影响我的关联点?
赛斯2012年

为了效率,上述目的也可以用于制造透明的IoC控制结构。是的,从理论上讲,在Java中可能会达到相同的结果,但这会极大地影响/混淆代码的编写方式-因此,我认为Scala推迟软件开发的许多要素的诀窍有助于我们朝着更快的代码迈进-更有可能是在实践中要快一些,而单元性能要稍快一些。
塞斯

好的,我重新阅读了一下,然后写了“简单的执行速度”-我会加一个注释。好点:)
塞斯(Seth)

3
可预测的if语句(在超标量处理器上基本上是免费的)与对象分配+垃圾的关系。Java代码显然更快(请注意,它仅评估条件,执行不会到达log语句。)响应“对于我的工作,琐碎对象的重量值得消除不必要评估日志消息的可能性”。
Eloff 2014年


10

Java和Scala都可以编译为JVM字节码,因此差别不大。您可以获得的最佳比较可能是在计算机语言基准测试游戏上,该游戏本质上说Java和Scala都具有相同的内存使用率。Scala是仅慢于Java的一些列出的基准,但可能仅仅是因为计划的实施是不同的。

确实,他们俩是如此亲密,因此不值得担心。通过使用更具表达力的语言(例如Scala)所带来的生产率提高,所带来的价值远远超过最小(如果有)的性能损失。


7
我在这里看到一个逻辑上的谬误:两种语言都可以编译为字节码,但是经验丰富的程序员和新手-他们的代码也可以编译为字节码-但不能编译为相同的字节码,因此得出的结论是,差异不可能那么大,可能是错误的。实际上,在过去,scala中的while循环可能比语义上等效的for循环快得多(如果我没记错的话,今天要好得多)。当然,两者都被编译为字节码。
用户未知,

@user未知-“在scala中,while循环可能比语义上等效的for循环快得多” –请注意,那些Scala基准测试游戏程序是使用while循环编写的。
igouy 2011年

@igouy:我不是在谈论这个微基准测试的结果,而是在争论。我要说明的问题Java and Scala both compile down to JVM bytecode, 与“ a”相结合,这是一个真实的陈述,它只是一个修辞手法,而不是一个有争议的结论。sodiffence isn't that big.so
用户未知,

3
高得令人惊讶的错误答案。
shabunc

4

Java示例实际上不是典型应用程序的惯用语。这样的优化代码可以在系统库方法中找到。但是,它将使用正确类型的数组,即File [],并且不会引发IndexOutOfBoundsException。(用于计数和加法的不同过滤条件)。我的版本是(总是(!)带有花括号,因为我不想花一个小时来搜索错误,该错误是由于节省2秒以在Eclipse中按一个键而引入的)。

List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
  if(s.length() > 2) {
    File file = mapping.get(s);
    if (file != null) {
      bigEnough.add(file);
    }
  }
}

但是我可以从当前项目中带给您许多其他丑陋的Java代码示例。我试图通过排除常见的结构和行为来避免常见的复制和修改编码风格。

在我的抽象DAO基类中,我有一个用于公共缓存机制的抽象内部类。对于每种具体的模型对象类型,都有一个抽象DAO基类的子类,其中该内部类被子类化,以提供一种方法的实现,该方法在从数据库加载业务对象时创建该业务对象。(我们不能使用ORM工具,因为我们通过专有API访问另一个系统。)

这种子类化和实例化代码在Java中根本不清楚,在Scala中可读性很强。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.