ElixirでIoT#1.2：いろいろ分かるベンチマークを整備してみる

この記事は「[Elixir or Phoenix Advent Calendar 2017](https://qiita.com/advent-calendar/2017/elixir-or-phoenix)」の12日目です．
昨日は@zacky1972さんの「[ZEAM開発ログv0.1.1 AI/MLを爆速にしたい！ Flow / GenStage でGPUを駆動できないの？](https://qiita.com/zacky1972/items/140d2380dfdf727b22bc)」でした．

# はじめに

こんにちは．
カーネルや低レイヤを触ることが多いので，[並行と並列の違いにはうるさい](https://twitter.com/TAKASEhideki/status/990835578231910400)ほうです．

先日の勉強会「[fukuoka.ex#8(福岡Elixir会)：2018年 春のElixir入学式](https://fukuokaex.connpass.com/event/85038/)」にて発表した内容を加筆編集しながら，**ElixirをIoTボードで動かしたらどうなんの？**という異色ネタをお届けしています．

これまでの記事では，IoTボード上でElixirが実行できる環境を構築してきました．

- [ElixirでIoT#1.0：IoTボードへのLinux環境の準備](https://qiita.com/takasehideki/items/9e3db412a40659233384)
- [ElixirでIoT#1.1：IoTボードへのElixir環境の構築とEEloTツールキットの紹介](https://qiita.com/takasehideki/items/fc570ae92a895caed213)（12/07版・05/01公開）

今回はがっつりElixirコードが出てきます．IoTボードの性能をいろいろ調べるために整備したベンチマークについて紹介します．
ちなみに，今回はIoT成分がゼロで，ボードに関係する内容は出てきません^^;

## ベンチマークとは？

こういう時は[Wikipedia先生に聞く](https://ja.wikipedia.org/wiki/ベンチマーク#コンピュータにおけるベンチマーク)のが手っ取り早いですよね．

> コンピュータの分野においては、コンピュータシステムのハードウェアやソフトウェアの性能を測定するための指標のことを指す。ひとつあるいは複数のプログラムを実行した結果をベンチマークスコアと呼び、ある対象に関する相対的な性能を表す指標として用いられる。
> 〜〜(略)〜〜
> ベンチマークは、異なる部品構成やアーキテクチャを持ちスペックなどによる直接的な性能比較ができないシステムの間において、様々な観点で性能を比較する手段を提供する。

本連載の内容に関係するところを抜粋してきましたが，バッチリの説明です．

# ベンチマークの紹介

ベンチマークアプリを整備するときに大事なことは，**どんな指標を評価／比較したいか？**です．
ということで，各種IoTボードの性能を評価するために今回整備した5種類のベンチマークアプリとそのココロについて紹介していきます．

## ライプニッツ級数

まずは単純なものから．円周率を求めるベンチマークアプリです．
**小数点演算の性能**を評価します．式は下記の通り．

```math
 \sum^{\infty}_{n=0}\frac{(-1)^{n}}{2n+1} = \frac{\pi}{4}
```

[こちらの記事](https://qiita.com/hanada/items/c91788bcac2a40f1bb05)にて@twinbeeさんが[コメント投稿されていたソース](https://qiita.com/hanada/items/c91788bcac2a40f1bb05#comment-b69e6585e1b86b5c03f7)を利用いたしました．
単一プロセスでの実行，和の上限$n$は引数で与えることにしています．

```leibniz_formula.ex
defmodule LeibnizFormula do
  def calc(n) do
    s = 0..n
        |> Enum.reduce( 0, fn (x, acc) -> acc + (:math.pow(-1, x) / (2*x + 1)) end )
    s = s*4
    #IO.puts s
  end
end
```

## フィボナッチ数列（シングル版）

お次はみんな大好きフィボナッチ数列です．
**整数演算およびスタック消費時の性能**を評価します．

```math
F_{0} = 0, \\
F_{1} = 1, \\
F_{n+2} = F{n} + F{n+1}
```

まずはシングルプロセスを用意しました．[こちらのブログ記事](http://blog.panicblanket.com/archives/3450)を参考にしました．
再帰計算が書きやすいのもElixirの特徴ですね．

```fibonacci_simple.ex
defmodule FibonacciSimple do

  def fib(n), do: fib_iter(0, 1, n, 1)

  defp fib_iter(_, _, n, i) when i > n, do: []
  defp fib_iter(a, b, n, i),            do: [a | fib_iter(b, a + b, n, i + 1)]

end
```

## フィボナッチ数列（マルチプロセス版）

Elixirを使うなら並列処理をさくっと書きたい！ということで，
**並列処理時の性能**を評価します．

みなさん[Dave ThomasのProgramming Elixir 1.2](https://www.amazon.co.jp/Programming-Elixir-Functional-Concurrent-Pragmatic/dp/1937785580)は読まれていますよね？Elixirプログラミング入門の決定版です．
この本の14.5節に出てくるフィボナッチ数列の複数プロセス版を用意しました．

ソースはコピペすると長くなりますので割愛します．
書籍の[support page](https://pragprog.com/titles/elixir12/source_code)からダウンロードしてきて，`./elixir12-code/spawn/fib.exs`を参照してください．

## CSVデータ処理

**大規模データの処理性能**を評価しましょう．

再び@twinbeeさんのお力添えを得て，巨大なCSVファイルをストリーム処理するアプリである`elixir_agg_csv`を用意しました．
詳細な説明は[こちらのGitHubリポジトリ](https://github.com/enpedasi/elixir_agg_csv)と[SlideShare](https://www.slideshare.net/YoshiiroUeno/elixir-go-c-x-scala-nodejs)をご参照ください．


キモとなるソースは`elixir_agg_csv/lib/elixir_flow.ex`です．Flowを用いて並列処理を実現しています．

```elixir_agg_csv/lib/elixir_flow.ex
defmodule ElixirFlow do
  def run( filename ) do
    start = Timex.now

    result = 
      filename
      |> File.stream!

      # データクレンジング
      |> Flow.from_enumerable()
      |> Flow.map( &( String.replace( &1, ",",    "\t" )       # ①CSV→TSV
                      |> String.replace( "\r\n", "\n" )        # ②CRLF→LF
                      |> String.replace(  "\"",   ""  ) ) )    # ③ダブルクォート外し
      # 集計
      |> Flow.map( &( &1 |> String.split( "\t" ) ) )          # ④タブで分割
      |> Flow.map( &Enum.at( &1, 2 - 1 ) )              # ⑤2番目の項目を抽出
      |> Flow.partition

      |> Flow.reduce( 
        fn -> %{} end, fn( name, acc )           # ⑥同値の出現数を集計
        -> Map.update( acc, name, 1, &( &1 + 1 ) ) end )
      |> Enum.sort( &( elem( &1, 1 ) > elem( &2, 1 ) ) )        # ⑦多い順でソート

  end
end
```

ちょっとカラースキーマが死んでますね，，，
[GitHubのソースブラウザ](https://github.com/enpedasi/elixir_agg_csv/blob/master/lib/elixir_flow.ex)でもご覧ください．


## Phoenixサーバ性能

やはりElixirといえばPhoenix，フレームワークの命名が素晴らしくて大好きです．
**Phoenixサーバのレイテンシとスループットの性能**を評価します．

今回は[こちらのGitHub](https://github.com/mroth/phoenix-showdown)にて公開されているアプリ`phoenix-showdown`を使うことにしました．
アプリの詳細解説（和文）は[こちらのブログ記事](http://muziyoshiz.hatenablog.com/entry/2016/12/24/205321)をご参照ください．

>
* URL ‘/:title’ の :title 部分をコントローラに渡す
* コントローラは、モデルの返り値を想定したマップ（固定値）の配列をビューに渡す
* ビューは、受け取った配列を回して HTML を生成し、クライアントに返す
* データベースへのアクセスは行わない
* ログ出力は行わない
* ENV = production で動作させる
* ベンチマークツールには wrk を使って、30秒間テストする
>

# やっぱりツールキット化

ベンチマーク環境を用意する際に，やっぱりいちいちコマンドを打ち込むのもメンドくないですか？なので今回もツールキットとして整備しました．

前回も紹介したEEloTツールキットには，`*.ex`ファイルの一括コンパイル・実行機能と，各リポジトリからベンチマーク環境を自動整備する機能が含まれています．**もうStarしていただけましたでしょうか！？**

- [EEloT: **E**valuation toolkit for **El**ixir on I<b>oT</b> board](https://github.com/takasehideki/EEloT)

使い方はやはりとっても簡単です．

## ベンチマーク環境の整備

途中で`7z`を使いますので`$ sudo apt install p7zip-full`とかしておいてください．

- `$ ./setup.sh`
    - `*.ex`の各ファイルをbeamにコンパイルします．
    - 各GitHubリポジトリからソースをcloneしてきて，mixプロジェクトをビルドします．
    - `elixir_agg_csv`の入力ファイル（CSV）もcloneして`7z`で解凍します．
- `$ ./setup.sh　exc`
    - `*.ex`の各ファイルのbeamへのコンパイルのみを行います．
- `$ ./setup.sh　clean`
    - beamファイルと各GitHubリポジトリのディレクトリを削除します．

## *.exの実行

ライプニッツ級数とフィボナッチ数列のアプリは，実行のためのスクリプトも用意しました．

```measure.ex
defmodule Measure do
  @module_list [[LeibnizFormula, :calc, [100_000_000]],
                [FibonacciSimple, :fib, [100000]],
                [Scheduler, :run, [10, FibSolver, :fib, [37,37,37,37,37,37]]]]

  def allex do
    IO.puts "Measurement start.\n"
    for [module,func,arg] <- @module_list do
      IO.puts "#{module}, #{func}, #{inspect(arg)}"
      IO.inspect {time, _} = :timer.tc(module, func, arg)
      IO.puts "\ntime\: #{time/(1000*1000)} second"
      IO.puts "----------"
    end
  end
end
```

`@module_list`は，評価対象のリストを表していて，`*.ex`に含まれるモジュール名，評価対象の関数名とその引数のリストです．
時間計測には`:timer.tc`を用いました．[引数の数によって定義が変わります](https://qiita.com/masashi127/items/d54688d9c9e25f057d61)が，今回の3引数だと`{module, function, arguments}`になります．返値は`{time, value}`のタプルです．

実は，今回の連載／fukuoka.exでの発表にあたって私が書いたソースはこれだけです^^;
なんせ筆者はElixir歴2週間ですので，こう書いたほうがいいんじゃね？とか，Elixirのクセしてfor文使ってんじゃね？とかのツッコミがありましたら，ぜひコメント欄でご指導ください．
（05/07追記：@piacereさんからコメントいただきました！）

# まとめ

- ベンチマークアプリ整備のキモは，どんな指標を評価／比較したいか？
- [EEloT](https://github.com/takasehideki/EEloT)を使えばベンチマーク環境の整備もお手のもの

# あれ？動かした結果はどうなるの？？

トランキーロ！あっせんなよっ．
いよいよ次回の記事では，ElixirベンチマークアプリをIoTボード上で動かした性能評価の結果と考察についてお送りします．そもそもIoTボードでPhoenixは立つことができるのか！？ご期待ください．

明日のAdvent Calendarは@piacere_exさんによる
「[Excelから関数型言語マスター3回目：WebにDBデータ表示](https://qiita.com/piacere/items/a7558adc6856e3577dc6)」
です．