Performance JIT在处理集合元素时如何优化分支?(以斯卡拉为单位)

Performance JIT在处理集合元素时如何优化分支?(以斯卡拉为单位),performance,scala,compiler-optimization,jit,Performance,Scala,Compiler Optimization,Jit,这是一个关于用Scala编写的代码性能的问题 考虑以下两个代码段,假设x是包含约5000万个元素的集合: def process(x: Traversable[T]) = { processFirst x.head x reduce processPair processLast x.last } 与类似的东西相比(假设现在我们有一些方法来确定我们是在第一个元素上操作还是在最后一个元素上操作): 哪种方法更快?对于大约5000万个元素,要快多少 在我看来,第一个示例会更快,因

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

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

def process(x: Traversable[T]) = {
   processFirst x.head
   x reduce processPair
   processLast x.last
}
与类似的东西相比(假设现在我们有一些方法来确定我们是在第一个元素上操作还是在最后一个元素上操作):

哪种方法更快?对于大约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
   }
}
虽然主体中没有额外的检查,但有一个额外的参考分配似乎是不可避免的(先前=当前)。这也是一种更为必要的方法,它依赖于可为空的可变变量。以功能性但高性能的方式实现这一点将是另一个问题的另一个练习


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

首先,请注意,根据
可遍历的
的具体实现,执行类似
x.last
的操作可能非常昂贵。比如,比这里发生的其他事情都要贵

第二,我怀疑条件本身的成本是否值得注意,即使在5000万个集合上也是如此,但实际上,根据实现情况,确定给定元素是第一个还是最后一个可能会再次变得昂贵

第三,JIT将无法优化条件:如果有一种方法可以做到这一点,您将能够在没有条件的情况下编写实现


最后,如果你开始看起来像一个额外的<代码> >如果< /Cord>语句可能影响性能,你可能会考虑切换到java或者甚至“C”。别误会我的意思,我喜欢scala,它是一种很棒的语言,有很多强大的功能和有用的特性,但是超级快速并不是其中之一

如果您的基础集合有一个便宜的
head
last
方法(对于通用的
可遍历的
来说不是这样),并且缩减操作相对便宜,那么第二种方法比我的机器上的第一种方法花费的时间长约10%(可能稍微少一点)。(您可以使用var来获取
第一个
,并且可以使用正确的参数不断更新第二个far来获取
最后一个
,然后在循环之外执行最后一个操作。)

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

大多数情况下,你不应该太担心它,而应该更多地担心它的正确性。例如,在2元素列表中,您的第二个代码有一个bug(因为有一个else而不是单独的测试)。在单元素列表中,第二个代码根本不调用reduce的lambda,因此再次失败

这表明,除非您确定
last
在您的情况下非常昂贵,否则您应该以第一种方式执行



编辑:如果使用迭代器切换到类似于手动reduce的操作,与昂贵的-
上一个案例(例如list)相比,您可能可以节省多达40%的时间。对于便宜的
最后一个
,可能没有那么多(高达~20%)。(例如,在对字符串长度进行操作时,我会得到这些值。)

实际上,在我的场景中,计算集合的第一个和最后一个元素并不昂贵。让我们假设,如果我们正在遍历一个链表,在到达该链表之前,我们不关心最后一个元素。感谢您抽出时间来进行基准测试,我仍在进行基准测试,并将很快报告我的结果以供比较。评论不用于进一步讨论;这段对话已经结束。
// 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
   }
}