21

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

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

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

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

  • 我读到构造很慢,因为每次执行循环体时都会for(...)生成一个匿名类。是真的吗?还有其他地方可以生成匿名类吗?(例如,与匿名函数一起使用时)map()
  • 在一般情况下,不可变集合是否比可变集合慢得多(尤其是在映射结构方面)?
  • Scala 2.8、2.9 和 2.10 之间是否存在显着的性能差异?
4

2 回答 2

36

过去我还必须优化很多 Scala 代码。以下内容并非完整列表,只是一些可能对您有所帮助的实际观察:

  • 是的,用 a 替换for循环while更快,即使使用 Scala 2.10。有关详细信息,请参阅评论中的链接谈话。另外,请注意,使用“过滤”(您正在迭代的集合之后的条件)将导致您的条件被装箱/拆箱,这可能会对性能产生很大影响(有关详细信息,请参阅此帖子)。

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

  • 到目前为止,我没有观察到 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
    

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

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

  • 在处理泛型类型时,@specialized为特定类型定义版本应该会加快速度。

  • 尽管我尽量避免一概而论,但我可以接受以下几点:尝试使用本机数组。在我的许多基准测试中,我最终都使用了数组,考虑到它们在 JVM 中的实现,这是有道理的。

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

于 2013-02-27T16:03:47.607 回答
3

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

于 2013-02-27T13:02:03.883 回答