この記事は「fukuoka.ex x ザキ研 Advent Calendar 2017」の3日目です。
昨日は @Takeshi-Kogu さんの「ElixirでExcelと同様のデータ処理をしてみた」でしたね。
はじめに
季節外れのアドベントカレンダーということで,「fukuoka.ex x ザキ研 Advent Calendar 2017」が6月18日から行われています。
今回は,その3日目の記事をElixir初心者の@zuminが書いていきたいと思います。
先日,6/16に開催された Erlang & Elixir Fest 2018 に参加してきました。
そこで,初心者向けハンズオンが開催され資料が公開されています。
ハンズオンの内容は,Elixirで並行プログラミングをやってみようというもので,一千万行のJSONデータが用意されていました。(一千万行もあると457.9 MB
にもなるんですね...)
そこで,最後の応用問題に
- 3種類の実装方法について「CPU,メモリ使用量,処理時間」を比較してみましょう。
とあるので,やってみようと思います。
動作環境
- macOS High Sierra 10.13.5
- MacBook Pro Retina 13-inch Mid 2014
- CPU 2.6 GHz Intel Core i5
- メモリ 16GB
- Elixir 1.6.4 (compiled with OTP 20)
ソースコード
実装方法については,ハンズオン資料にあります。
JSONファイルも同様にハンズオン資料内で提供されています。
defmodule Basic do
def q1 do
"data.json"
|> File.stream!
|> Enum.map(fn d -> Poison.decode!(d) end)
|> Enum.filter(fn d -> d["age"] <= 20 end)
|> Enum.count
end
def q2_1 do
"data.json"
|> File.stream!
|> Stream.map(fn d -> Poison.decode!(d) end)
|> Stream.filter(fn d -> d["age"] <= 20 end)
|> Enum.count
end
def q2_2 do
"data.json"
|> File.stream!
|> Flow.from_enumerable
|> Flow.map(fn d -> Poison.decode!(d) end)
|> Flow.partition
|> Flow.filter(fn d -> d["age"] <= 20 end)
|> Enum.count
end
end
実行結果
まずはコンパイル。
$ mix compile
そして,以下のように実行しました。
$ /usr/bin/time mix run -e "Basic.q1"
$ /usr/bin/time mix run -e "Basic.q2_1"
$ /usr/bin/time mix run -e "Basic.q2_2"
real | user | sys | 消費メモリ | |
---|---|---|---|---|
q1 | 78.10 s | 68.34 s | 9.75 s | 2.6 GB |
q2_1 | 69.23 s | 64.12 s | 1.62 s | 28 MB |
q2_2 | 45.60 s | 116.39 s | 6.49 s | 45 MB |
遅延処理を行うか行わないかによる消費メモリの差がとても大きいものとなりましたね。
終わり
Elixirで一千万行のJSONファイルをパースしてfilterするとこんな結果となりました。
2コア4スレッド CPUでも,実行速度には結構な差がでる結果となりました。
また,遅延処理を行うことによるメモリ消費量についても大きな差がでることが分かりました。
real | user | sys | 消費メモリ | |
---|---|---|---|---|
通常 | 78.10 s | 68.34 s | 9.75 s | 2.6 GB |
遅延処理 | 69.23 s | 64.12 s | 1.62 s | 28 MB |
並行処理 | 45.60 s | 116.39 s | 6.49 s | 45 MB |
Elixirで関数型言語に初挑戦しましたが,オブジェクト指向言語とはまた違った面白さを少しずつ感じながらやっていっています。
これからどんどん踏み込んだ内容をやっていきたいですね。
おまけ
もとのJSONファイルだとJSON.parse
ができなかったので,配列の形にしてパースできるようにしています。
- ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-darwin16]
require 'json'
data = File.read('data.json')
results = JSON.parse(data)
under_twenty = results.select{ |result| result['age'] <= 20 }
under_twenty.count
real | user | sys | 消費メモリ |
---|---|---|---|
65.66 s | 60.05 s | 3.66 s | 4.0 GB |
メモリは,遅延処理を行っていない場合のElixirよりもだいぶん消費していますが,実行時間に関しては結構いい勝負をしていますね。
Rubyでの遅延処理や並行処理は,そのうち...
明日は @zacky1972 さんの「ZEAM開発ログ v.0.2.0 Node.js と同じ原理の軽量コールバックスレッドを Elixir に実装してみた (背景編)」です。お楽しみに。