Erlang R17 の名前つきローカル関数の速度

2014年4月9日にErlangの17.0がリリースされました。
17.0では新しいプリミティブタイプのMapsと専用のシンタックスが追加されて、そこそこ古い言語なのに意欲的だなぁという風に見ていました。


で、もう一つの大きな変更として、こちらのサイトでも紹介されているようにローカル関数に名前を付けられるようになりました。
先ほどのサイトの例でもあるように、これによってローカル関数の自己再帰呼出が書きやすくなります。
ローカル関数で自己再帰呼出が出来るということは関数内のループ処理が書きやすくなるので、既存コードのループ部分を書き換えるかどうかを判断するためにテストをしてみました。


テストは単純なループにどれぐらい時間がかかるかを計測したものです。
メモリ使用量は測っていないので、本来ならメモリ使用量も測るべきかな。
以下、テストに使用したコード。

loop_test(C) ->
  io:format("loop1:~p~n", [timer:tc(?MODULE, loop1, [C, 0])]),
  io:format("loop2:~p~n", [timer:tc(?MODULE, loop2, [C, 0])]),
  io:format("loop3:~p~n", [timer:tc(?MODULE, loop3, [C, 0])]),
  ok.

loop1(0, N) ->
  N;
loop1(C, N) ->
  loop1(C-1, N+1).

loop2(C, N) ->
  (fun Loop(0, NN) ->
      NN;
    Loop(CC, NN) ->
      Loop(CC-1, NN+1)
  end)(C, N).

loop3(C, N) ->
  loop(fun(_, 0, NN) ->
        NN;
      (Loop, CC, NN) ->
        Loop(Loop, CC-1, NN+1)
    end, C, N).

-spec loop(fun((fun(), any(), any()) -> any()), any(), any()) -> any().
loop(Fun, Arg1, Arg2) ->
  Fun(Fun, Arg1, Arg2).

loop1はグローバルに定義した関数の再帰呼出。一番速いはずなので基準として使用します。
loop2は新しい書き方を利用した関数。今回はこれの速度を計りたい。
loop3は既存コードで書いているやり方。ユーティリティ関数経由して無名関数に名前を与えている。


で、以下が実行して出力された結果。

loop_test(10000000).
loop1:{178750,10000000}
loop2:{764379,10000000}
loop3:{306386,10000000}
ok

新しい書き方をした場合、ユーティリティ関数を経由するやり方と比べても2倍以上かかっている。
そして、既存コードのやり方がそんなに遅くないということも今回初めて知った。


比べると速度の差はあるが、一回当たりの差にしてしまうと微々たるものなので気にするほどでもないかもしれない。
しかしせっかく新しい機能なのになんとなくがっくり。
configure時の引数を間違えたんじゃないかとも思ってしまう。
気長に待ってたら最適化されるようになるのかな?