在这个 PR 中, Jose Valim 的对于 beam_ssa 模块中的 def_user_1 函数的修改, 使得巨大函数的编译时间大大减少. 让我们来看看他究竟做了哪些修改.
这里是旧的代码:
def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, Used0) ->
{Def,Used1} = def_used_is(Is, Preds, Def0, Used0),
Used = ordsets:union(used(Last), Used1),
def_used_1(Bs, Preds, Def, Used);
def_used_1([], _Preds, Def, Used) ->
{ordsets:from_list(Def),Used}.
这是修改后的代码:
def_used_1([#b_blk{is=Is,last=Last}|Bs], Preds, Def0, UsedAcc) ->
{Def,Used} = def_used_is(Is, Preds, Def0, used(Last)),
case Used of
[] ->
def_used_1(Bs, Preds, Def, UsedAcc);
[_|_] ->
def_used_1(Bs, Preds, Def, [Used|UsedAcc])
end;
def_used_1([], _Preds, Def0, UsedAcc) ->
Def = ordsets:from_list(Def0),
Used = umerge(UsedAcc),
{Def,Used}.
我们来一步一步地对比修改前后的代码, 首先看 match pattern, 基本没有变化, 除了把 Used0 改成了UsedAcc, 方便阅读理解.
第二行的改动就比较大了, 这样看可能不容易理解, 我们先跳过, 先看第二个 pattern 的函数体:
{ordsets:from_list(Def),Used}. %% old
Def = ordsets:from_list(Def0),
Used = umerge(UsedAcc),
{Def,Used}. %% new
对于 Def 的处理没有变化, 而新的代码中使用一次 umerge(这里是 lists:umerge)函数来处理 UsedAcc, 得到 Used. 在官方文档, 我们查到这个 lists:umerge 的功能是:
Returns the sorted list formed by merging all the sublists of ListOfLists. All sublists must be sorted and contain no duplicates before evaluating this function. When two elements compare equal, the element from the sublist with the lowest position in ListOfLists is picked and the other is deleted.
简单来说就是把多个 list 结合起来, 同时有排序和去重的功能, 例如:
A = [1, 2, 3].
B = [3, 4, 5].
C = [4, 5, 6].
[1,2,3,4,5,6] = lists:umerge([A,B,C]).
[1,2,3,4,5,6] = lists:umerge([C,B,A]).
回到代码, 我们可以知道, 新的代码是把排序和去重以及 merge 放到了最后一步来做. 再对比第一个 pattern的函数体, 我们发现 def_used_is 函数的定义没有变, 但最后一个参数变了. 理解这个改动, 我想我们需要理解 ordsets:union 函数的功能. ordsets 顾名思义就是排序好的 sets, union函数的功能类似 merge, 就是把两个 ordsets 结合起来, 并排序, 去重, 例如:
O1=ordsets:from_list([1,2,3]).
O2=ordsets:from_list([3,4,5]).
[1,2,3,4,5]=ordsets:union(O1, O2).
[1,2,3,4,5]=ordsets:union(O2, O1).
看到这里, 我们大概能够知道这个 PR 的性能优化点了: 原始代码中, 如果 Bs 的长度是 100, 那么就要做 100 次的排序,去重,结合, 而新代码中, 不管 Bs 多长, 都只要最后做一次排序,去重,结合. 所以对于 Bs 巨大的情况, 会有很好的优化.