优化Scala代码时首先要查看的位置?

时间:2013-02-27 12:51:39

标签: scala optimization

我目前需要优化一个太慢的算法的Scala实现。它以功能方式实现,仅使用值(val)和不可变数据结构。我已经记住了重要的函数(因此我的代码中有一些可变映射),这使我的代码速度提高了一倍,我不知道接下来要做什么。

所以,我不是在寻找关于软件优化的通用建议(例如,首先优化你的算法,使用分析器,做基准测试......)而是寻找 Scala特定或JVM特定的优化建议

因此,我的问题是在尝试优化Scala代码时首先要查看?什么是 常见的 语言结构或模式通常导致速度减慢

特别是,我正在就以下几点寻求建议:

  • 我读到for(...)构造很慢,因为生成了一个匿名类每次执行循环体的时间。这是真的吗?是否有其他地方生成匿名类? (例如,使用匿名函数map()时)
  • 不可变集合显着比一般的可变集合慢 case(特别是涉及到地图结构时)?
  • Scala 2.8,2.9和2.10之间是否存在显着的性能差异?

2 个答案:

答案 0 :(得分:34)

我过去也必须优化很多Scala代码。以下并不是一个完整的清单,只是一些可能对您有帮助的实际观察:

  • 是的,即使使用Scala 2.10,用for替换while循环也会更快。有关详细信息,请参阅linked talk in the comments。另外,请注意使用“for filtering”(您正在迭代的集合后面的条件)将导致条件的框/拆箱,这会对性能产生很大影响(see this post for details)。

  • 不可变与可变的问题只能通过您必须执行的更新次数来回答,而且(对我而言)在这里给出一般性答案很困难。

  • 到目前为止,我没有观察到2.8,2.9和2.10之间的显着性能差异。但显然这取决于手头的问题。例如,如果你的算法大量使用Range.sum,你会发现很大的差异(因为这在2.10中现在是O(1))。

  • 我注意到使用相应的Java集合而不是Scala版本也会导致显着的加速(如我所说的那样,大概是5-10%)。例如,我在微基准测试中得到了以下结果(显示为运行时),以解决一个非常具体的问题(注意:不要从中推广;运行自己的)。

    ColtBitVector          min:      0.042    avg:      0.245    max:     40.120
    JavaBitSet             min:      0.043    avg:      0.165    max:      4.306
    JavaHashSet            min:      0.191    avg:      0.716    max:     12.624
    JavaTreeSet            min:      0.313    avg:      1.428    max:     64.504
    ScalaBitSetImmutable   min:      0.380    avg:      1.675    max:     13.838
    ScalaBitSetMutable     min:      0.423    avg:      3.693    max:    457.146
    ScalaSetImmutable      min:      0.458    avg:      2.305    max:      9.998
    ScalaSetMutable        min:      0.340    avg:      1.332    max:     10.974
    

    手头的问题是计算整数集的简单交集(具有非常特定的大小和集合数)。我要展示的是:选择正确/错误的集合会产生重大影响!同样,我认为很难给出一般建议选择哪种数据类型,因为这只能告诉我们这个特殊交集问题的性能(但我确实选择了Java的HashSet以上的几个案例替代方案)。另请注意,此交集问题不需要可变数据类型。然而,即使在不可变的功能中也存在性能差异(而关于Set,它是可变集合,它明显更快,它是BitSet的不可变集合。因此,根据情况,您可能希望选择一个可变集合而不是不可变的集合以获得最大性能(小心使用!)。

  • 有人告诉我,声明变量private[this] var foo = ...会阻止getter / setter函数的创建,并且应该更快(免责声明:我从未在微基准测试中证实这一点)。

  • 在处理泛型类型时,为特定类型定义@specialized版本会导致加速。

  • 虽然我试图避免泛化,但我可以使用以下内容:尝试使用本机数组。在我的许多基准测试中,我最终都使用了Arrays,考虑到它们在JVM中的实现,这是有道理的。

  • 我想到的一个小问题:我观察到使用伴随对象(即origCollection.toSomeCollectionName)通过SomeCollectionName(origCollection :_*)构建集合与构建集合的差异。在许多情况下,后者明显更快。

答案 1 :(得分:3)

您的代码在运行时是否实例化了大量对象?例如,Scala case类或map / flatMap的链可能会导致创建大量“不必要”的对象。这可能会减慢代码速度并对垃圾收集器施加更多的工作。