特别是关于模式匹配和案例类。请考虑以下事项:
abstract class Expr
case class Var(name: String) extends Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
case class BinOp(operator: String, left: Expr, right: Expr) extends Expr
object Expr {
def simplify(expr: Expr): Expr = expr match {
// Some basic simplification rules...
case UnOp("-", UnOp("-", e)) => simplify(e) // Double negation
case BinOp("+", e, Number(0)) => simplify(e) // Adding zero
case BinOp("-", e, Number(0)) => simplify(e) // Subtracting zero
case BinOp("*", e, Number(1)) => simplify(e) // Multiplying by one
case BinOp("*", e, Number(0)) => Number(0) // Multiplying by zero
case _ => expr // Default, could not simplify given above rules
}
}
如果有任何样本调用,例如simplify(UnOp("-", UnOp("-", UnOp("-", UnOp("-", Var("x"))))))
(产生Var("x")
),匹配表达式中备选方案的顺序是否对性能有影响?
旁注,一种相关的(我自己的观察):关于simplify
真正让我感到震惊的一点是,它是一个递归函数,虽然不像其他递归函数我书面/处理,基本案例是最后一个,以避免提前终止。
答案 0 :(得分:8)
理论上是的,因为匹配测试是按顺序完成的。
但在实践中,差异可能无关紧要。我使用Caliper和你的例子运行了微基准测试。我将Var("x")
加上100'000 Unop
的前缀,以使其更大。
结果是:
[info] 0% Scenario{vm=java, trial=0, benchmark=ForFirst} 240395.82 ns; σ=998.55 ns @ 3 trials
[info] 50% Scenario{vm=java, trial=0, benchmark=ForLast} 251303.52 ns; σ=2342.60 ns @ 5 trials
[info]
[info] benchmark us linear runtime
[info] ForFirst 240 ============================
[info] ForLast 251 ==============================
在第一次测试中,UnOp
个案是第一个,第二个测试是最后一个(就在默认情况之前)。
正如你所看到的,它并不重要(慢不到5%)。也许是这样,有了大量的案例清单,但这也是重构的候选人。
完整代码在此处:https://gist.github.com/1152232(通过scala-benchmarking-template运行)。
答案 1 :(得分:7)
如上所述的匹配语句被转换为字节码中的一堆if语句:
public Expr simplify(Expr);
Code:
0: aload_1
1: astore_3
2: aload_3
3: instanceof #17; //class UnOp
6: ifeq 110
. . .
110: aload_3
111: instanceof #35; //class BinOp
114: ifeq 336
. . .
所以它实际上等同于按顺序运行一堆if语句。因此,与if语句一样,首先放置常见案例可能有所帮助。编译器在折叠类似测试方面做得相当不错,但它并不完美,所以有时候它可以更好地捕获多个案例(甚至使用嵌套的if语句),并且可以使用某种类型的决策树。仍然,编译器做做得相当不错,所以不要浪费你的时间,除非你知道这是瓶颈。
答案 2 :(得分:0)
列出案例的顺序不一定是测试案件的顺序。常量(包括null)由编译器排序,以便在其中快速搜索。因此,在处理常量时按使用频率对案例进行排序是没有意义的。但是,在匹配类型时,顺序至关重要:即使稍后会更好地匹配(不太通用),也将使用第一个匹配的类型。因此,最具体的类型应该放在首位。