为什么OCaml可以生成用于模式匹配的高效机器代码而不用于if-else测试?
我正在阅读Real World OCaml,我遇到了this部分,他们将模式匹配的性能与if-else测试的性能进行了比较。事实证明,他们的例子中的模式匹配明显快于if-else测试。即使代码没有使用if-else测试无法实现的任何特殊模式匹配情况,它只是比较整数。
他们将模式匹配的编译器优化归因于性能差异的原因。编译器将能够根据有效选择的运行时检查集生成直接跳转到匹配案例的机器代码。
我理解这一点,但我不明白为什么编译器不能为if-else测试做同样的事情。毕竟,代码只是将整数与整数进行比较。这是因为OCaml还没有(还)优化if-else测试,或者是因为它不可能优化if-else测试可能与模式匹配一样吗?
模式匹配代码如下所示:
let plus_one_match x =
match x with
| 0 -> 1
| 1 -> 2
| 2 -> 3
| _ -> x + 1
if-else代码如下所示:
let plus_one_if x =
if x = 0 then 1
else if x = 1 then 2
else if x = 2 then 3
else x + 1
答案 0 :(得分:6)
match
和if
具有不同的语义:match
是并行的,if
是严格顺序的。如果你有表达式:
match expr with
| A -> e1
| B -> e2
| C -> e3
| ...
然后它可以按任何顺序比较分支。在我提供的示例中,它可以编译为二进制搜索,利用这样的事实,构造函数表示为oridinary数字。
在表达式
中if e1 then e2 else if e3 then e4 else ...
语言的语义要求e3
在e1
之后严格评估,iff e1
评估为false。这意味着,编译器无法重新排列比较顺序,因为它无法知道e1
true
是false
的{{1}}(如果它知道,那么就是if表达式将通过不断折叠来修剪,所以它并不重要。
回到你的例子。如果您编译匹配函数,您将获得以下代码(在x86_64上):
.L104:
cmpq $5, %rax
jbe .L103
addq $2, %rax
ret
.L103:
sarq $1, %rax
cmpq $1, %rax
je .L101
jg .L100
.L102:
movq $3, %rax
ret
.L101:
movq $5, %rax
ret
.L100:
movq $7, %rax
ret
这实际上对应于表达式:
if x > 2 then x + 1
else match compare x 1 with
| 0 -> 2
| 1 -> 3
| _ -> 1
相当高效的代码,根本不使用两个比较。并且在运行时它通常(取决于数据分布)完成一次比较。
如果您使用if
编译示例,那么它将发出代码,该代码基本上等于原始OCaml代码。因为,编译器需要通过if
表达式的语义来执行此操作。 if
表达式必须是顺序的。
有人可以说,假设比较函数是内置比较函数,if
示例可以编译为相同的代码。但是为此,编译器需要证明比较函数是内置的或者没有副作用。仅适用于int
的一个特定情况。我怀疑,有人会写这样的优化,因为它不值得。
match
表达式的另一个显着特征是匹配可以在非常有限的一组对象上执行,这些对象共享一个共同点,它们都是序数,或者可以被排序。在if
表达式中,我们有任意表达式。
答案 1 :(得分:5)
当然,可以以相同的方式优化if-else测试。例如,您可以编写一个分为两个阶段的新优化器:首先将所有if-else测试转换为模式匹配(尽管在OCaml中),然后运行现有的编译器。
因此,答案必须是在编译器开发人员的优先级列表中优化if-else测试的方式并不高。
由于未来版本的编译器可能会为if-else测试带来更好的优化,我现在只会更改时间关键代码。