【问题标题】:How fast is pattern matching in ScalaScala 中的模式匹配有多快
【发布时间】:2016-09-14 21:45:04
【问题描述】:

我是 Scala 新手。当我通过阅读其他人编写的 Scala 代码来学习它时,我发现 Scala 代码与其他语言不同的最显着特征之一就是它的模式匹配。

在感受到它带来的便利性和表现力的同时,我不禁好奇它背后的潜在性能成本——一般来说match有多快?

首先,没有“高级”功能,例如构造函数的匹配参数,IMO Scala 中的match 是其他语言中的switch-case 的对应物。例如,

color match {
  0 => println "color is red!"
  1 => println "color is green!"
  2 => println "color is blue!"
}

作为一个新手,我想知道上面的代码是否和if-else语句中的等效代码一样快?

其次,现在收回那些“高级”功能,例如:

expr match {
  Animal(name) => ...
  Animal(name, age) => ...
  Animal(_, _, id) => ...
}

至于上面的代码或者match的其他特性(列表匹配、配对匹配等),我很好奇Scala是如何实现这些花哨的用法的?最重要的是,我期望这些代码有多快? (比如说,它们仍然和第一种情况下的match 一样快吗?或者可能稍微慢一点?或者由于使用了反射等技术而变得非常慢?)

提前致谢!

【问题讨论】:

  • 取决于上下文:在涉及常量的有限情况下,它就像一个 java 开关。在涉及内置案例类模式的模式匹配的情况下,它只是一个实例检查,然后是字段的成员查找。在一般情况下,会调用完整的 unapply 方法(是的,这会更慢)。

标签: scala


【解决方案1】:

第一个 sn-p 被翻译成字节码的TableSwitch(或LookupSwitch)并且和Java的switch/case一样快:

scala> def foo(i: Int) = i match {
     | case 1 => 2
     | case 2 => 10
     | case 3 => 42
     | case _ => 777
     | }
foo: (i: Int)Int

scala> :javap -c foo
Compiled from "<console>"
public class  {
  public static final  MODULE$;

  public static {};
    Code:
       0: new           #2                  // class
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public int foo(int);
    Code:
       0: iload_1
       1: istore_2
       2: iload_2
       3: tableswitch   { // 1 to 3
                     1: 44
                     2: 39
                     3: 34
               default: 28
          }
      28: sipush        777
      31: goto          45
      34: bipush        42
      36: goto          45
      39: bipush        10
      41: goto          45
      44: iconst_2
      45: ireturn

  public ();
    Code:
       0: aload_0
       1: invokespecial #18                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #20                 // Field MODULE$:L;
       8: return

第二个片段被翻译成一堆unapply/isInstanceOf/null checks 调用,并且(显然)比tableswitch 慢。但它具有与通过isInstanceOf 手动检查相同(或更好,如果编译器可以优化某些东西)的性能(没有反射或类似的东西):

scala> case class Foo(s: String, i: Int)
defined class Foo

scala> def bar(foo: Foo) = foo match {
     | case Foo("test", _) => 1
     | case Foo(_, 42) => 2
     | case _ => 3
     | }
bar: (foo: Foo)Int

scala> :javap -c bar
Compiled from "<console>"
public class  {
  public static final  MODULE$;

  public static {};
    Code:
       0: new           #2                  // class
       3: invokespecial #12                 // Method "<init>":()V
       6: return

  public int bar(Foo);
    Code:
       0: aload_1
       1: astore_2
       2: aload_2
       3: ifnull        26
       6: aload_2
       7: invokevirtual #20                 // Method Foo.s:()Ljava/lang/String;
      10: astore_3
      11: ldc           #22                 // String test
      13: aload_3
      14: invokevirtual #26                 // Method java/lang/Object.equals:(Ljava/lang/Object;)Z
      17: ifeq          26
      20: iconst_1
      21: istore        4
      23: goto          52
      26: aload_2
      27: ifnull        49
      30: aload_2
      31: invokevirtual #30                 // Method Foo.i:()I
      34: istore        5
      36: bipush        42
      38: iload         5
      40: if_icmpne     49
      43: iconst_2
      44: istore        4
      46: goto          52
      49: iconst_3
      50: istore        4
      52: iload         4
      54: ireturn

  public ();
    Code:
       0: aload_0
       1: invokespecial #34                 // Method java/lang/Object."<init>":()V
       4: aload_0
       5: putstatic     #36                 // Field MODULE$:L;
       8: return
}

【讨论】:

  • 仅仅查看匹配 case 构造函数模式的模式不太公平,因为它有自己的特殊处理 - 如果您要编写自己的 unapply 模式,我认为 javap 转储将是丑多了。
  • 好吧,没那么难看。它类似于第二个字节码 sn-p(isInstanceOf,空检查),但没有可能的优化。 “Scala 编译器可以比提取器更好地优化案例类的模式。这是因为案例类的机制是固定的,而提取器中的 unapply 或 unapplySeq 方法几乎可以做任何事情”(来自“Scala 编程”)。
  • 我猜想至少有一些 Option[(..)] 选项元组对象会在未优化的情况下保留(因为可以反驳该模式,与只需要 instanceof 和 null 检查的构造函数模式不同),这似乎效率相对较低。
  • 是的,据我所知。你也可能想看看github.com/scala/scala/pull/2848(关于OptInt的段落)。
猜你喜欢
  • 1970-01-01
  • 2013-03-17
  • 2012-10-31
  • 2016-05-17
  • 2021-12-17
  • 2016-07-07
  • 2013-12-01
  • 2016-03-29
  • 1970-01-01
相关资源
最近更新 更多