Scala的不可变对象创建的成本

时间:2014-12-05 12:46:18

标签: scala immutability

我在[1]中看到了类似for-comprehension的帖子,这让我想知道使用不可变Map与Mutable的整体含义是什么。似乎Scala开发人员非常习惯于允许不可变数据结构的突变产生新对象的成本 - 或者我可能只是遗漏了某些东西。如果对不可变数据结构的每个变异操作都返回一个新实例,虽然我理解它对线程安全有好处,但是如果我知道如何微调我的可变对象已经做出同样的保证呢?

[1] In Scala, how can I do the equivalent of an SQL SUM and GROUP BY?

3 个答案:

答案 0 :(得分:7)

通常,回答这类性能问题的唯一方法是在实际代码中对它们进行分析。微博客通常具有误导性(例如this benchmarking tale) - 特别是如果您谈论并发性,最佳策略可能会有很大不同,具体取决于您的用例在实践中的并发性。

理论上,Sufficiently Smart Compiler™应该能够 - 可能借助于线性类型系统(推断或其他方式) - 来重现可变数据结构的所有效率优势。事实上,由于它有更多关于程序员意图的信息,并且受到程序员必须指定的附带细节的限制,因此这样的编译器应该能够生成更高性能的代码 - 例如, GCC将代码重写为不可变形式(SSA)以进行优化。对于一个更接近家庭的例子,许多真实的Java程序具有完全足够的吞吐量,但是由于Java的垃圾收集器阻止世界压缩堆而导致​​延迟问题。知道某些对象是不可变的JVM将能够在不停止世界的情况下移动它们(您可以简单地复制该对象,更新对它的所有引用,然后删除旧副本,因为它不重要,如果一些线程看到旧版本,而其中一些线程看到新版本。)

在实践中,它取决于而且唯一的方法是对您的具体案例进行基准测试。根据我的经验,对于大多数实际业务问题可用的程序员时间投入水平,在(不可变的)Scala版本上花费x小时往往会产生比在可变Scala上花费更多时间更高效的程序或Java版本 - 实际上,在生成可接受性能的Scala版本所花费的程序员时间量上,根本不可能完成Java版本(特别是如果我们需要相同的缺陷率)。另一方面,如果你有无限的专家程序员时间并且需要获得绝对最佳性能,你可能想要使用一种非常低级的可变语言(这就是为什么LAPACK仍然用Fortran编写) - 甚至正如JP Morgan最近所做的那样,直接在FPGA上实现您的算法。

但即使在这种情况下,您可能希望使用更高级语言的原型,以便您可以编写测试并比较两者以确认高性能实现是否正常工作。特别是如果我们只是谈论Scala中的可变与不可变,那么过早的优化就是所有邪恶的根源。编写您的程序,然后如果性能不足,请对其进行分析并查看热点。如果你真的花太多时间复制一个不可变的数据结构,那么现在是用可变版本替换它的合适时间,并仔细检查线程安全保证。如果您正在编写正确的解耦代码,那么在您需要时可以轻松地替换性能关键部分,并且在此之前您可以获得更简单,更容易的代码的开发时间收益。原因(特别是在并发情况下)。根据我的经验,编写良好的代码中的性能问题比人们预期的要少得多;大多数软件性能问题都是由于算法或数据结构选择不当造成的,而不是这种小开销。

答案 1 :(得分:2)

基于对使用不可变对象产生的成本的误解,您的问题始于错误的假设。

使用构建为不可变对象的保证不可变对象允许您使用结构共享,因此您可以基于旧对象创建新对象,而无需求助对于对象的深层副本,您可以粗略地说,重用新的对象的部分内容。 因此,这大大减轻了使用不可变对象的影响。

那么微调,手工制作的可变物体有什么区别?

  • 不可变对象更适合FP范例
  • 编译时优化和检查
  • 降低运行时异常的可能性

答案 2 :(得分:1)

这个问题非常通用,所以很难给出明确的答案。看起来你只是对用于理解等的惯用scala代码中发生的对象分配量感到不舒服。

scala编译器没有做任何特殊的魔术来融合操作或忽略对象分配。编写数据结构的人员应确保功能数据结构尽可能多地重用以前的版本(结构共享)。 scala集合中使用的许多数据结构都做得相当好。例如,请参阅关于Functional Data Structures in Scala的讨论,以便为您提供一般性的想法。

如果您对详细信息感兴趣,可以通过Chris Okasaki获取 一书来获取Purely Functional Data Structures。本书中的内容也适用于其他功能语言,如Haskell,OCaml和Clojure。

JVM非常擅长分配和收集短期对象。对于习惯于低级编程的人来说,许多看起来非常低效的事情实际上是非常有效的。但肯定存在可变状态具有性能或其他优点的情况。这就是为什么scala不禁止可变状态,而只是偏向于不变性。如果由于性能原因发现您确实需要可变状态,那么wrap your mutable state in an akka actor通常是一个好主意,而不是试图获得正确的低级线程同步。

相关问题