cnDenis http://cndenis.github.io 2014-05-30

在cowboy的库cowlib中的cow_inline.hrl文件看到一堆很长的宏:

-define(INLINE_LOWERCASE(Function, Rest, Acc),
    $A -> Function(Rest, << Acc/binary, $a >>);
    $B -> Function(Rest, << Acc/binary, $b >>);
    $C -> Function(Rest, << Acc/binary, $c >>);
    $D -> Function(Rest, << Acc/binary, $d >>);
    $E -> Function(Rest, << Acc/binary, $e >>);
    $F -> Function(Rest, << Acc/binary, $f >>);
    $G -> Function(Rest, << Acc/binary, $g >>);
    $H -> Function(Rest, << Acc/binary, $h >>);
    $I -> Function(Rest, << Acc/binary, $i >>);
    $J -> Function(Rest, << Acc/binary, $j >>);
    $K -> Function(Rest, << Acc/binary, $k >>);
    $L -> Function(Rest, << Acc/binary, $l >>);
    $M -> Function(Rest, << Acc/binary, $m >>);
    $N -> Function(Rest, << Acc/binary, $n >>);
    $O -> Function(Rest, << Acc/binary, $o >>);
    $P -> Function(Rest, << Acc/binary, $p >>);
    $Q -> Function(Rest, << Acc/binary, $q >>);
    $R -> Function(Rest, << Acc/binary, $r >>);
    $S -> Function(Rest, << Acc/binary, $s >>);
    $T -> Function(Rest, << Acc/binary, $t >>);
    $U -> Function(Rest, << Acc/binary, $u >>);
    $V -> Function(Rest, << Acc/binary, $v >>);
    $W -> Function(Rest, << Acc/binary, $w >>);
    $X -> Function(Rest, << Acc/binary, $x >>);
    $Y -> Function(Rest, << Acc/binary, $y >>);
    $Z -> Function(Rest, << Acc/binary, $z >>);
    C -> Function(Rest, << Acc/binary, C >>)
).

底下还有2参数,3参数…10参数的版本.

找到一个调用的地方看看: cowboy_protocol.erl

parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, V, H, SoFar) ->
case C of
    $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, SoFar);
    $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
    $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
    ?INLINE_LOWERCASE(parse_hd_name, Rest, S, M, P, Q, V, H, SoFar)
end.

还是看不明白, 用c(cowboy_protocol, ['P'])查看展开的宏, 上面的代码变成了:

parse_hd_name(<<C,Rest/bits>>, S, M, P, Q, V, H, SoFar) ->
    case C of
        $: ->
            parse_hd_before_value(Rest, S, M, P, Q, V, H, SoFar);
        $  ->
            parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
        $\t ->
            parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
        $A ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$a>>);
        $B ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$b>>);
        $C ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$c>>);
        $D ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$d>>);
        $E ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$e>>);
        $F ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$f>>);
        $G ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$g>>);
        $H ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$h>>);
        $I ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$i>>);
        $J ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$j>>);
        $K ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$k>>);
        $L ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$l>>);
        $M ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$m>>);
        $N ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$n>>);
        $O ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$o>>);
        $P ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$p>>);
        $Q ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$q>>);
        $R ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$r>>);
        $S ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$s>>);
        $T ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$t>>);
        $U ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$u>>);
        $V ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$v>>);
        $W ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$w>>);
        $X ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$x>>);
        $Y ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$y>>);
        $Z ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,$z>>);
        C ->
            parse_hd_name(Rest, S, M, P, Q, V, H, <<SoFar/binary,C>>)
    end.

嗯, 这样就明白多了, 就是进行程序的逻辑处理同时, 把大写字母转换成小写.

测试一下这种写法的效率, 把一个大写的二进制串转成小写. Erlang没有原生的对二进制串做大小写转换的方法, 所以要先转列表, 转小写后再转回来.

%% 先转列表
lower_by_list(B) ->
    list_to_binary(string:to_lower(binary_to_list(B))).

%% inline的方法
lower_by_inline(B) ->
    lower_by_inline(B, <<>>).
lower_by_inline(<<C, R/bits>>, Acc) ->
    case C of
        ?INLINE_LOWERCASE(lower_by_inline, R, Acc)
    end;
lower_by_inline(<<>>, Acc) ->
    Acc.

对于1K字节的大写二进制串, 运行结果如下:

len: 1024
bench:lower_by_list/1
Single Process:      29925 call per sec,      32768 times in  1095 ms

bench:lower_by_inline/1
Single Process:      25090 call per sec,      32768 times in  1306 ms

如果单是做大小写转换, 这么做并不比先转列表再转回来快, 不过是可以省一点内存.

想起Erlang文档里的一句话: Do not guess about performance 出自这里. 这里用了相当复杂的方法完成了一个简单的工作, 或许是针对项目需求极度优化的结果, 如果不顾场合照搬, 那就是给自己添麻烦了.



blog comments powered by Disqus
头像

cnDenis

Email: 联系我

Fork me on GitHub