(この記事は、「fukuoka.ex x ザキ研 Advent Calendar 2017」の22日目です)
昨日は@piacere_exさんの「関数型でデータサイエンス#4:インプットしたデータを集約する②」でした.
#はじめに
前回の記事「Elixirで2次元のリスト加算関数をつくる」では,2次元リストの加算関数について説明しました。
- ガード句とパターンマッチングは併用できる
- 2次元リストだと表示が上手くいかないことがあるので注意
今回の記事は打って変わり,再帰呼び出しによるリストの操作以外にも,Enum
やFlow
で記述したコードの実行速度を測定した結果を示します.
実行速度測定
実行速度の測定には: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)のときは最適化もされていない再帰呼び出しでは及ばなさそうです.
最後に
今回は実行速度を測定しました.再帰呼び出しが最速とも言い切れない,まとまりのない情報でしたが,実行速度を測ってみたいという方の参考になれば幸いです.
参考文献
-
Elixir School「Erlangとの相互運用」
-
masashi127 「Elixirでパフォーマンス測定」
明日は @Takeshi-Kogu さんの「モジュールをファイルに書き込んでみる(後半)」です。お楽しみに!