ErlangとCRC32 (つづき)

先日のErlangと配列の続きです。

あらすじ: zlib:crc32と、自前で実装したCRC32の性能を比較しました。自前で実装したCRC32は内部のテーブルの持ちかたをいろいろ変えて性能の違いを計ってみました。当たり前ですがzlibのものが一番早かったです。

今回はそんなに関係ないあらすじ: 一口にCRC32といってもいろんなバリエーションがあるので、IEEE 802.3 のものでなかったらzlib:crc32は使えないから嫌だなあと思い、もし自分で実装するとなるとErlangで配列に相当するデータ構造が必要だよね、でもいくつか思いつく手段のうちどれをとればいいのだろう?と疑問に思いました。(あらすじに続く)

Voluntasさんから、erlang:crc32/1 を使えばというコメントをいただきました。ありがとうございます。

(また、Voluntasさんには、Efficiency Guideの日本語版も紹介いただきました。ありがとうございます。OTP Design Principles と System Principles もあって感激です。)

さて、CRC32がBIFにあったなんて...盲点でした。盲点がでかすぎる気がしますね。恥ずかしいです。

あらためて計測しなおしてみました。まずMac:

loop tuple binary2 dict process_dictionary ets zlib erlang_crc
1 5734 7671 13954 5658 12408 430 429
2 5763 7673 14028 5624 12488 457 453
3 5726 7616 13994 5612 12368 428 429
4 5762 7682 14031 5673 12432 427 428
5 5755 7594 14014 5691 12567 430 429

次にWindows:

loop tuple binary2 dict process_dictionary ets zlib erlang_crc
1 7691 10685 22681 7969 16769 483 46
2 7471 10514 22744 8034 19671 529 46
3 8439 13368 23447 8316 17050 483 61
4 7643 10561 22697 8033 16988 483 61
5 7783 10576 22963 8220 16898 483 46

最後のerlang_crcというのがerlang:crc32を使ったときの結果です。前回と同様、10万回ずつ試しています。前回との違いは、

  • 試行を5回(つまり10万回を5回)に減らしました(前回は10回でした)
  • 前回没にしたbinary2を採用しています(くわしくは前回の記事を御覧下さい)

といったところです。Windows版のerlang_crcが目の覚めるようなスピードで動いていますね... ertsのソースを軽く見た限りではMacWindowsでそんなに劇的に変わる気配は感じられなかったので、もう少し読んでみる必要がありそうです。


ソースを再度載せます。erlang_crc を追加したのと、あとははてなダイヤリーに載せやすいように整形する関数(pp/1)を用意したくらいしか違いはありません。

-module(crc).
-compile(export_all).

-define(POLY_IEEE802_3, 16#EDB88320).

times(0, _, Res) ->
    lists:reverse(Res);
times(N, F, Res) when N > 0 ->
    times(N - 1, F, [F(N) | Res]).

pp(L) ->    
    io:format(
      ["|" ++
           [io_lib:format("~p|", [XX]) || XX <- X]
       ++ "\n" || X <- L]).

bench_all(Loop, Times) ->
    bench_tests(Loop, Times, [tuple, binary2, dict, process_dictionary, ets, zlib,erlang_crc]).

bench_tests(Loop, Times, Tests) ->
    Data = lists:seq(0,255),
    Bin = list_to_binary(Data),
    times(Loop,
	  fun(N) ->
		  lists:reverse(
		    lists:foldl(
		      fun(Type, List) ->
			      [ bench(Times, Type, Bin) div 1000 | List]
		      end, [(Loop - N + 1)], Tests))
	  end, [[loop|Tests]]).

bench(Times, Type, Bin) when Type =:= zlib orelse Type =:= erlang_crc ->
    Expected = crc(tuple, binary_to_list(Bin)),
    BBench = now(),
    do_bench(Times, Type, void, Bin, Expected),
    EBench = now(),
    timer:now_diff(EBench, BBench);
bench(Times, Type, Bin) when is_binary(Bin) -> 
    bench(Times, Type, binary_to_list(Bin));
bench(Times, Type, List) when is_list(List) ->
    Expected = crc(tuple, List),
    Table = make_table(Type, ?POLY_IEEE802_3),
    BBench = now(),
    do_bench(Times, Type, Table, List, Expected),
    EBench = now(),
    timer:now_diff(EBench, BBench).

do_bench(0, _Type, _Table, _List, _Expected) -> ok;
do_bench(Times, Type, Table, List, Expected) ->
    Expected = do_crc(Type, List, Table),
    do_bench(Times - 1, Type, Table, List, Expected).

calc_table(Poly, I) ->
    lists:foldl(
      fun(_J, U) ->
	      case (U band 1) of
		  1 ->
		      (U bsr 1) bxor Poly;
		  0 ->
		      U bsr 1
	      end
      end, I, lists:seq(0,7)).
calc_index(Res, B) ->
    (B bxor (Res band 16#ff)).
calc_crc1(Res, TableValue) ->
    (Res bsr 8) bxor TableValue.

crc(zlib, Bin) when is_binary(Bin) ->
    do_crc(zlib, Bin, void);
crc(Type, Bin) ->
    crc(Type, Bin, ?POLY_IEEE802_3).

crc(Type, Bin, Poly) when is_binary(Bin) ->
    List = binary_to_list(Bin),
    crc(Type, List, Poly);    
crc(Type, List, Poly) when is_list(List) ->
    Table = make_table(Type, Poly),
    do_crc(Type, List, Table).


make_table(tuple, Poly) ->
    lists:foldl(
      fun(I, Tuple) ->
	      erlang:append_element(Tuple, calc_table(Poly, I))
      end, {}, lists:seq(0, 255));
make_table(binary, Poly) ->
    lists:foldl(
      fun(I, Binary) ->
	      <<Binary/binary, (calc_table(Poly, I)):32>>
      end, <<>>, lists:seq(0, 255));
make_table(binary2, Poly) ->
    make_table(binary, Poly);
make_table(dict, Poly) ->
    lists:foldl(
      fun(I, Dict) ->
	      dict:store(I, calc_table(Poly, I), Dict)
      end, dict:new(), lists:seq(0, 255));
make_table(process_dictionary, Poly) ->
    lists:foldl(
      fun(I, _) ->
	      put(I, calc_table(Poly, I))
      end, void, lists:seq(0, 255));
make_table(ets, Poly) ->
    Table = ets:new(crc, [set]),
    lists:foreach(
      fun(I) ->
	      ets:insert(Table, {I, calc_table(Poly, I)})
      end, lists:seq(0, 255)),
    Table.

do_crc(tuple, List, Table) ->
    CRC = lists:foldl(
	    fun(B, Res) ->
		    Index = calc_index(Res, B),
		    calc_crc1(Res, element(Index + 1, Table))
	    end, 16#ffffffff, List),
    (bnot CRC) band 16#ffffffff;
do_crc(binary, List, Table) ->
    CRC = lists:foldl(
	    fun(B, Res) ->
		    Index = calc_index(Res, B),
		    <<TableValue:32>> = binary:part(Table, Index * 4, 4),
		    calc_crc1(Res, TableValue)
	    end, 16#ffffffff, List),
    (bnot CRC) band 16#ffffffff;
do_crc(binary2, List, Table) ->
    CRC = lists:foldl(
	    fun(B, Res) ->
		    Index = calc_index(Res, B) * 4,
		    <<_:Index/binary, TableValue:32, _/binary>> = Table,
		    calc_crc1(Res, TableValue)
	    end, 16#ffffffff, List),
    (bnot CRC) band 16#ffffffff;
do_crc(dict, List, Table) ->
    CRC = lists:foldl(
	    fun(B, Res) ->
		    {ok, TableValue} = dict:find(calc_index(Res, B), Table),
		    calc_crc1(Res,  TableValue)
	    end, 16#ffffffff, List),
    (bnot CRC) band 16#ffffffff;
do_crc(process_dictionary, List, _Table) ->
    CRC = lists:foldl(
	    fun(B, Res) ->
		    TableValue = get(calc_index(Res, B)),
		    calc_crc1(Res, TableValue)
	    end, 16#ffffffff, List),
    (bnot CRC) band 16#ffffffff;
do_crc(ets, List, Table) ->
    CRC = lists:foldl(
	    fun(B, Res) ->
		    Index = calc_index(Res, B),
		    [{Index, TableValue}] = ets:lookup(Table, Index),
		    calc_crc1(Res, TableValue)
	    end, 16#ffffffff, List),
    (bnot CRC) band 16#ffffffff;
do_crc(zlib, Bin, _Table) ->
    Z = zlib:open(),
    Res = zlib:crc32(Z, Bin),
    zlib:close(Z),
    Res;
do_crc(erlang_crc, Bin, _Table) ->
    erlang:crc32(Bin).