Scala表现问题

nan*_*nda 10 performance scala scala-collections

Daniel Korzekwa撰写文章中,他表示以下代码的表现:

list.map(e => e*2).filter(e => e>10)
Run Code Online (Sandbox Code Playgroud)

比使用Java编写的迭代解决方案更糟糕.

有谁能解释为什么?什么是Scala中此类代码的最佳解决方案(我希望它不是Scala-fied的Java迭代版本)?

Rex*_*err 15

原因在于特定的代码是缓慢的,因为它的工作对原语,但它使用通用的操作,因此,元都被装箱.(如果List它的祖先是专门的,这可以得到改善.)这可能会使事情减慢5倍左右.

此外,从算法上讲,这些操作有点昂贵,因为你制作了一个完整的列表,然后创建一个全新的列表,抛弃中间列表的一些组件.如果你一举做到这一点,那么你会更好.你可以这样做:

list collect (case e if (e*2>10) => e*2)
Run Code Online (Sandbox Code Playgroud)

但如果计算e*2真的很贵怎么办?那你可以

(List[Int]() /: list)((ls,e) => { val x = e*2; if (x>10) x :: ls else ls }
Run Code Online (Sandbox Code Playgroud)

除了这会出现倒退.(如果需要,你可以反转它,但这需要创建一个新列表,这在算法上也不是理想的.)

当然,如果你使用的是单链表,那么你在Java中就会遇到同样的算法问题 - 你的新列表最终会被倒退,或者你必须创建两次,首先是反向然后转发,或者你有与(非尾)递归(这是很容易在Scala中,但不妥当的这种事情在任何一种语言,因为你会耗尽堆栈)建造它,或者你必须创建一个可变的列表,然后假装以后,它的不可变的.(顺便说一下,你也可以在Scala中做 - 看看mutable.LinkedList.)


Wil*_*ger 13

基本上它是两次遍历列表.一次将每个元素乘以两个.然后另一次过滤结果.

可以把它想象成一个while循环,生成一个带有中间结果的LinkedList.然后应用过滤器的另一个循环产生最终结果.

这应该更快:

list.view.map(e => e * 2).filter(e => e > 10).force
Run Code Online (Sandbox Code Playgroud)

  • 我现在可以很聪明,并指出在示例代码中没有任何地方它说它涉及Ints.但我不得不承认,如果它提到有相当多的拳击和拆箱正在进行,答案会更好. (3认同)

Dan*_*ral 6

解决方案主要在于JVM.虽然Scala在图中有一个解决方法,但@specialization它大大增加了任何专业类的大小,并且只解决了一半的问题 - 另一半是临时对象的创建.

JVM实际上很好地优化了很多,或者性能会更糟糕,但Java不需要Scala所做的优化,因此JVM不提供它们.我希望通过SAM在Java中引入非实时闭包来改变某种程度.

但是,最终,它归结为平衡需求.同样的while循环,它的Java和Scala这样做的速度远远超过了Scala的功能相当于在C.更快尚未完成然而,尽管什么微基准测试告诉我们,人们使用Java.