我目前需要优化一个太慢的算法的Scala实现。它以功能方式实现,仅使用值(val
)和不可变数据结构。我已经记住了重要的函数(因此我的代码中有一些可变映射),这使我的代码速度提高了一倍,我不知道接下来要做什么。
所以,我不是在寻找关于软件优化的通用建议(例如,首先优化你的算法,使用分析器,做基准测试......)而是寻找 Scala特定或JVM特定的优化建议
因此,我的问题是在尝试优化Scala代码时首先要查看?什么是 常见的 语言结构或模式通常导致速度减慢?
特别是,我正在就以下几点寻求建议:
for(...)
构造很慢,因为生成了一个匿名类每次执行循环体的时间。这是真的吗?是否有其他地方生成匿名类? (例如,使用匿名函数map()
时)答案 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
的链可能会导致创建大量“不必要”的对象。这可能会减慢代码速度并对垃圾收集器施加更多的工作。