Edited at

Elixirで関数の実行速度を測定する

More than 1 year has passed since last update.

(この記事は、「fukuoka.ex x ザキ研 Advent Calendar 2017」の22日目です)

昨日は@piacere_exさんの「関数型でデータサイエンス#4:インプットしたデータを集約する②」でした.



はじめに

 前回の記事「Elixirで2次元のリスト加算関数をつくる」では,2次元リストの加算関数について説明しました。


  • ガード句とパターンマッチングは併用できる

  • 2次元リストだと表示が上手くいかないことがあるので注意

今回の記事は打って変わり,再帰呼び出しによるリストの操作以外にも,EnumFlowで記述したコードの実行速度を測定した結果を示します.


実行速度測定

 実行速度の測定には:timer.tcを使います.以下は与えられた関数の処理時間を単位[s]で表示するコードです.

こちらの記事(Elixirでパフォーマンス測定)を参考にさせていただきました.

defmodule Test

 ...
# サンプルコード
def timed(func) do
func
|> :timer.tc
|> elem(0)
|> Kernel./(1000000)
end
...
end

 

上記のコードに少し手を加えて,強引に実行速度を測定します.

Flowのコードはこちらの記事(Elixir Flowでlazyな並列分散処理)を参考にさせていただきました.

defmodule Test

def timed_recursive() do
:timer.tc( fn ->
1..10_000_000
|> Enum.to_list
|> Numex.twice end )
|> elem(0)
|> Kernel./(1_000_000)
end

def timed_enum() do
:timer.tc( fn ->
1..10_000_000
|> Enum.to_list
|> Enum.map(& &1 * 2 ) end )
|> elem(0)
|> Kernel./(1_000_000)
end

def timed_flow(stage) do
:timer.tc(fn ->
1..10_000_000
|> Flow.from_enumerable(stages: stage)
|> Flow.map(& &1 * 2)
|> Enum.to_list
|> Enum.sort end)
|> elem(0)
|> Kernel./(1_000_000)
end

# 要素ごと2倍
def twice([a|tla]) when not is_list(a) do
[a*2|twice( tla)]
end
def twice([]), do: []
end


確認

使用したデータは,(A) 一千万もしくは,(B) 一億の要素をもつリストです.

実行速度[s]
(A)10_000_000
(B)100_000_000

Recursive
1.0722782
27.61662

Enum
1.2500004
17.484315

Flow(1)
3.5802442
41.678115

Flow(2)
6.6561348
123.548497

※一千万の方の結果は10回実行した平均値,一億の方は1回実行した結果です.

処理自体が単純なものなので,Flowで並列化させても効果は薄そうです.(並列化するコストの方が高そう)

Enumと比較してみると,(A)のときは,いい勝負になってますが,(B)のときは最適化もされていない再帰呼び出しでは及ばなさそうです.


最後に

今回は実行速度を測定しました.再帰呼び出しが最速とも言い切れない,まとまりのない情報でしたが,実行速度を測ってみたいという方の参考になれば幸いです.


参考文献

明日は @Takeshi-Kogu さんの「モジュールをファイルに書き込んでみる(後半)」です。お楽しみに!