JIT如何在处理集合元素的同时优化分支? (在斯卡拉)

时间:2015-01-23 00:45:50

标签: performance scala compiler-optimization jit

这是一个关于用Scala编写的代码性能的问题。

考虑以下两个代码片段,假设x是包含约5000万个元素的集合:

def process(x: Traversable[T]) = {
   processFirst x.head
   x reduce processPair
   processLast x.last
}

这样的事情(假设现在我们有办法确定我们是在第一个元素还是最后一个元素上运行):

def isFirstElement[T](x: T) = ???
def isLastElement[T](x: T) = ???

def process(x: Traversable[T]) = {
   x reduce { 
     (left, right) => 
       if (isFirstElement(left)
         processFirst(left)
       else if (isLastElement(right))
         processLast(right)

       processPair(left, right)
   }
}

哪种方法更快?对于约5000万个元素,速度快多少?

在我看来,第一个例子会更快,因为除了第一个和最后一个元素之外,所有条件检查都会发生。然而,对于后一个例子,有一些论据表明JIT可能足够聪明,可以优化那些除了第一个/最后一个元素之外的所有其他头部/最后条件检查。

JIT是否足够聪明地执行此类操作?后一种方法的明显优点是所有业务都可以放在同一个函数体中,而在后一种情况下,业务必须分成三个单独调用的独立函数体。

** 编辑 **

感谢所有出色的回复。当我离开上面的第二个代码片段来说明其不正确性时,我想略微修改第一种方法以更好地反映我试图解决的问题:

// x is some iterator
def process(x: Iterator[T]) = {
   if (x.hasNext)
   {
       var previous = x.next
       var current = null
       processFirst previous
       while(x.hasNext)
       {
          current = x.next
          processPair(previous, current)
          previous = current
       }
       processLast previous
   }
}

虽然正文中没有进行额外的检查,但还有一个额外的参考分配似乎是不可避免的(先前=当前)。这也是一种更加迫切需要的方法,它依赖于可空的可变变量。以功能性和高性能的方式实现这一点将是另一个问题的另一个练习。

这段代码片段如何与上述两个示例中的最后一个叠加? (包含所有分支的单迭代块方法)。我意识到的另一件事是两个例子中的后一个在包含少于两个元素的集合上被打破。

2 个答案:

答案 0 :(得分:2)

如果您的基础集合具有便宜的headlast方法(对于通用Traversable不适用),并且还原操作相对便宜,那么第二种方式大约需要10比我机器上的第一个更长(可能少一点)。 (您可以使用var来获取first,并且可以使用正确的参数继续更新第二个以获取last,然后在循环之外执行最后的操作。)

如果你有一个昂贵的last(即你必须遍历整个集合),那么第一个操作需要大约10%的时间(可能多一点)。

大多数情况下,你不应该过多担心它,而是更担心正确性。例如,在2元素列表中,您的第二个代码有一个错误(因为有一个else而不是单独的测试)。在1元素列表中,第二个代码根本不会调用reduce的lambda,所以再次无法工作。

这是争辩说你应该以第一种方式去做,除非你确定last在你的情况下真的很贵。


编辑:如果你使用迭代器切换到手动类似于reduce的操作,那么与昂贵的last情况(例如列表)相比,你可能能够节省大约40%的时间。对于便宜的last,可能没那么多(高达~20%)。 (例如,在操作字符串长度时,我会得到这些值。)

答案 1 :(得分:1)

首先,请注意,根据Traversable的具体实施情况,执行x.last之类的操作可能非常。比如,比其他所有的东西都要贵。

其次,我怀疑条件本身的成本是否会引人注目,即使是在一个5000万集合上,但实际上确定一个元素是第一个还是最后一个,可能会再次,取决于实现,变得昂贵。

第三,JIT将无法优化条件:如果有办法,你可以编写你的实现,无条件开始。

最后,如果你开始看起来像额外的if语句可能会影响性能,你可能会考虑切换到java甚至" C"。不要误会我的意思,我喜欢斯卡拉,这是一种很棒的语言,有很多力量和有用的功能,但超快速不仅仅是其中之一。