【问题标题】:Scala pattern matching performanceScala 模式匹配性能
【发布时间】:2023-03-12 18:59:02
【问题描述】:

我在 coursera "scala specialization" 做作业时遇到了这个问题(这是简化版,不包含任何作业细节,只是数组遍历)

val chars: Array[Char] = some array

def fun1(idx:Int):Int = {
    some code here (including the stop condition)

    val c = chars(idx)
    c match{
      case '(' => fun1(idx+1)
      case  _  => fun1(idx+1)
    }

}

这段代码比

慢 4 倍
def fun2(idx: Int):Int = {
    some code here (including the stop condition)

    val c = chars(idx)
    (c == '(') match{
      case true => fun2(idx+1)
      case  _  => fun2(idx+1)
    }
}

我所做的只是改变模式匹配 (我使用 ScalMeter 运行它,所以我相信统计数据)。

谁能解释这种行为?

【问题讨论】:

    标签: performance scala pattern-matching


    【解决方案1】:

    我只能确认第一个 match 慢了约 50%,而不是 4 倍 (2.11.8)。无论如何,如果您查看字节码,您会发现第一个match 被翻译为tableswitch 指令,这通常用于Java 的switch 语句有多个选择,基本上是一个查找goto,而第二个被翻译到if。所以第二个match 很简单:

    if (c == '(') fun2(idx+1) else fun2(idx+1)
    

    【讨论】:

      【解决方案2】:

      更新下面是错误的(这些测试中的大部分时间都花在生成数据上,因此实际遍历时间的差异并不明显。使用恒定输入运行相同的基准测试显示〜125ms case ')' 情况下每 1 亿个条目,而其他情况下约 35 毫秒。)

      我没有看到您描述的差异。不知道 ScalaMeter 是如何做到的,但是在 repl 中运行它(在通过运行“dry”几次让“热身”之后),我得到了几乎相同的性能:

      def func(s: Seq[Char], idx: Int): String = 
        if(idx == s.length) "foo" else s(idx) match {
          case ')' => func(s, idx+1)
          case _ => func(s, idx+1)
        }
      
      def func1(s: Seq[Char], idx: Int): String = 
        if(idx == s.length) "foo" else (s(idx) == '(') match {
          case true  => func(s, idx+1)
          case _ => func(s, idx+1)
        }
      
      import scala.util.Random
      def randy = Stream.continually(Random.nextPrintableChar)
      
      
      def doit(n: Int)(f: (Seq[Char], Int) => String) = {
       val start = System.currentTimeMillis;       
       f(randy.take(n).toIndexedSeq, 0); 
       System.currentTimeMillis - start
      }
      
      
      scala> doit(1000000)(func)
      res9: Long = 231
      
      scala> doit(1000000)(func1)
      res10: Long = 238
      
      scala> doit(1000000)(func)
      res11: Long = 234
      
      scala> doit(1000000)(func1)
      res12: Long = 201
      

      等等。如您所见,没有太大区别。

      【讨论】:

      • 我怀疑这是否接近有效的基准测试方法。 ScalaMeter 确实喜欢几十次热身,直到结果稳定。你甚至不使用相同的数据进行测试。
      • 是的,数据不一样。但是我在两次运行之间得到了非常接近的结果。 stddev 非常低,这表明如果我使用相同的数据,我不会看到太大的差异。正如答案中提到的,我也做过热身,所以不确定这个基准你觉得“无效”怎么样。我坚持我的发现,如果可以的话,我会挑战你最终(并且可重复地)反驳它们。
      • 借用你的函数,这里是 scala 仪表的基准:gist.github.com/lukaszwawrzyk/a2505d5b3083bb72de51b8445fbb9a76 Giving char time: 13.172258374999998 msbool time: 4.739404575 ms。如果使用索引 seq 结果确实更接近但不相等(95 秒与 80 秒),则它是针对问题中的数组完成的
      • @Łukasz 你是对的。事实证明,我实验中的大部分时间都花在了生成数据上。使用常量数组运行它确实显示出 ~4 倍的运行时间差异。
      猜你喜欢
      • 2011-02-06
      • 2016-05-17
      • 2021-12-17
      • 2011-06-13
      • 1970-01-01
      • 2014-09-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多