LoginSignup
35
23

More than 5 years have passed since last update.

Elixirで一千万行のJSONデータで遊んでみた

Last updated at Posted at 2018-06-20

この記事は「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ファイルも同様にハンズオン資料内で提供されています。

lib/basic.ex
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 に実装してみた (背景編)」です。お楽しみに。

35
23
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
23