斯卡拉与Java,性能和内存?

我热衷于研究Scala,并有一个基本的问题,我似乎找不到答案:一般来说,Scala和Java之间的内存在性能和使用方面有差异吗?

Scala可以很容易地使用大量的内存,而不会意识到这一点。 这通常是非常强大的,但偶尔也会令人讨厌。 例如,假设您有一个string数组(称为array ),并将这些stringmapping到文件(称为mapping )。 假设你想得到地图中的所有文件,并且来自长度大于2的string。 在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); } 

呼! 努力工作。 在斯卡拉,做同样事情的最简洁的方法是:

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

简单! 但是,除非您对集合的工作原理相当熟悉,否则您可能不会意识到,这样做会创build一个额外的中间数组(使用filter ),并为数组的每个元素创build一个额外的对象(使用mapping.get ,这将返回一个选项)。 它还创build了两个函数对象(一个用于filter,另一个用于flatMap),但由于函数对象很小,所以这很less成为主要问题。

所以基本上,内存使用情况在原始级别是一样的。 但斯卡拉的图书馆有许多强大的方法,可以让你很容易地创build大量(通常是短暂的)对象。 垃圾收集器通常对于那种垃圾非常好,但如果你完全忘记了正在使用什么内存,那么在Scala中可能比Java更快地遇到问题。

请注意,计算机语言基准游戏Scala代码是为了获得类似于Java的性能而以类似于Java的风格编写的,因此具有类似于Java的内存使用情况。 您可以在Scala中这样做:如果您将代码编写成高性能Java代码,那么它将是高性能的Scala代码。 (你也许可以用更习惯的斯卡拉风格来编写它,并且仍然可以获得良好的性能,但这取决于具体情况。)

我应该补充说,在编程的每一段时间,我的Scala代码通常比我的Java代码快,因为在Scala中,我可以用更less的工作量完成繁琐的不关键性能的部分,并且花更多的精力来优化algorithm,代码为性能关键部分。

我是一个新用户,所以我不能给Rex Kerr的答案添加评论(允许新用户“回答”而不是“评论”是一个非常奇怪的规则btw)。

我只是为了回应“呃,Java是如此冗长而且如此艰苦的工作”而回应了Rex的stream行答案。 虽然你当然可以编写更简洁的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)); 

现在,我不仅节省了input完整variables名称和大括号的时间(使我花费更多的时间思考深层的algorithm思想),而且还可以在混淆比赛中input我的代码,并有可能获得额外的现金假期。

像Java一样写下你的Scala,你可以期待几乎相同的字节码被发送 – 几乎相同的度量。

把它写得更“惯用”,用不变的对象和更高阶的函数,它会慢一点,稍微大些。 这种经验法则的一个例外是使用types参数使用@specialised注释的通用对象,这将创build更大的字节码,避免装箱/取消装箱,从而超过Java的性能。

另外值得一提的是,在编写可以并行运行的代码时,更多的内存/更less的速度是不可避免的折衷。 习惯性的Scala代码本质上比典型的Java代码更具声明性,而且通常只有4个字符( .par ),而不是完全平行。

因此,如果

  • 在单个线程中,Scala代码比Java代码长1.25倍
  • 它可以很容易地拆分成4个核心(即使在笔记本电脑中也是常见的)
  • 并行运行时间为原始Java(1.24 / 4 =)0.3125x

那么你会说Scala代码现在比较慢了25%,还是快了3倍?

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

计算机语言基准游戏:

速度testing java / scala 1.71 / 2.25

内存testing java / scala 66.55 / 80.81

所以,这个基准testing表明,java的速度提高了24%,scala使用了21%的内存。

总而言之,没有什么大不了的,在大多数时间都被数据库和networking占用的现实世界的应用程序中应该没有关系。

底线:如果斯卡拉让你和你的团队(以及在你离开的时候把项目放在一边)更有成效,那么你应该去做。

其他人已经回答了这个问题,关于严格的循环,虽然似乎有一个明显的性能差异之间雷克斯克尔的例子,我已经评论。

这个答案实际上是针对那些可能研究devise缺陷的紧圈优化需求的人。

我对Scala相对来说比较新(大约一年左右),但到目前为止,它的感觉是,它可以让你相对容易地推迟devise,实现和执行的许多方面(有足够的背景知识和实验:)

延期devise特点:

  • 抽象types
  • 显式键入的自引用
  • 查看
  • 混入

延期实施特点:

  • 差异注释
  • 化合物types
  • 局部types推断

延期执行function:(对不起,没有链接)

  • 线程安全的懒惰值
  • 传址名称
  • Monadic的东西

对我来说,这些function是帮助我们走上快速,紧密应用的道路。


Rex Kerr的例子在执行的哪些方面是延迟的。 在Java示例中,内存分配被推迟到计算大小时,Scala示例将映射查找延迟。 对我来说,他们看起来像完全不同的algorithm。

下面是我认为更像是一个苹果苹果等同于他的Java例子:

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

没有中间集合,没有Option实例等。这也保留了集合types,所以bigEnough的types是Array[File]Array的集合实现可能会按照Kerr先生的Java代码所做的工作来完成。

上面列出的延期devisefunction也允许Scala的收集API开发人员在未来的发行版中实现这个快速的特定于Array的收集实现,而不会破坏API。 这就是我所说的通过加快速度的方法。

也:

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

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


在Scala中简单执行速度的一个例子就是日志logging。

在Java中,我们可能会写如下的东西:

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

在斯卡拉,这只是:

 logger.debug("trace") 

因为要在Scala中debugging的消息参数的types是“ => String ”,我认为这是一个无参数函数,它在计算时执行,但文档调用按名称传递。

编辑{斯卡拉函数是对象,所以这里有一个额外的对象。 对于我的工作来说,一个微不足道的对象的权重值得去掉日志消息得到不必要的评估的可能性。 }

这不会使代码更快,但它确实使得代码更快,我们也不太可能拥有整个清理其他人代码的经验。

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


硬代码无法捕捉为什么Scala更快,但它有点提示。

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

在Java中,令人敬畏的代码经常被迫成为一个难以理解的混乱,因此在生产质量API中不可行,因为大多数程序员将无法使用它。

我非常希望Scala能够让我们之间的爱因斯坦能够实现更多能干的API,可能通过DSL来expression。 Scala中的核心API已经走在了这条路上。

Java和Scala都编译成JVM字节码,所以差别不是那么大。 你可以得到的最好的比较可能是在计算机语言基准游戏 ,基本上说,Java和斯卡拉都有相同的内存使用情况。 在列出的一些基准testing中,Scala只比Java慢一些,但这可能只是因为程序的实现是不同的。

实际上,他们都很近,不值得担心。 通过使用像Scala这样更具performance力的语言,您所获得的生产力提高远远超过了最低限度(如果有的话)的性能。

@higherkinded关于这个主题的介绍 – 做一些Java / Scala比较的Scala性能注意事项 。

工具:

  • ScalaMeter
  • 斯卡拉-基准模板

伟大的博客:

  • Nanotrusting Nanotime

Java示例实际上不是典型应用程序的成语。 这样的优化代码可以在系统库方法中find。 但是,它会使用正确types的数组,即File [],并不会抛出IndexOutOfBoundsException。 (用于计数和添加的不同的过滤条件)。 我的版本将(总是(!)与花括号,因为我不喜欢花一个小时search一个错误,通过保存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基类中,我有一个普通caching机制的抽象内部类。 对于每个具体的模型对象types,都有一个抽象DAO基类的子类,其中内部类被子类化,以便为从数据库加载时创build业务对象的方法提供实现。 (我们不能使用ORM工具,因为我们通过专有API访问另一个系统。)

这个子类和实例化代码在Java中并不完全清楚,并且在Scala中非常易读。