(この記事は、「fukuoka.ex(その2) Elixir Advent Calendar 2017」の1日目,および「python Advent Calendar 2017」の22日目です)
昨日は @twinbee さんの「Elixirから簡単にRustを呼び出せるRustler #2 クレートを使ってみる」でしたね。
おしらせ
お礼:各種ランキングに39回のランクインを達成しました
4/27~5/19までの23日間,毎日お届けした「季節外れのfukuoka.ex Elixir or Phoenix Advent Calendar」 は,Qiitaトップページトレンドランキングに3回,「はてなブックマーク」のホットエントリー「テクノロジー」カテゴリに2回もランクインし,他ランキングも含めると,トータル39回ものランクインを果たしました
Qiita「いいね」数は合計322件もいただき,fukuoka.exアドバイザーズとfukuoka.exキャストの一同,みなさまの暖かい応援に励まされていますので,引き続き,「季節外れのfukuoka.ex(その2) Elixir Advent Calendar」でも応援お願いします
さて本題〜はじめに
本連載の前回記事はこちら
|> ZEAM開発ログv0.1.0 Flow / GenStage による並列プログラミング入門
|> ZEAM開発ログv0.1.1 AI/MLを爆速にしたい! Flow / GenStage でGPUを駆動できないの?
|> ZEAM開発ログv0.1.2 AI/MLを爆速にしたい! Flow のコードを OpenCL で書いてみる〜CPU編
|> ZEAM開発ログv0.1.3 AI/MLを爆速にしたい! Flow のコードを OpenCL で書いてみる〜GPU編
まとめとしては次の通りです。
- OpenCLでGPUを利用した時にはC言語の1並列と比べて3.95倍の速度向上,Elixirの同等プログラムと比べて10.8〜11.9倍の速度向上になりました。
- OpenCLを使わずにマルチコアかつSIMD命令やAVX命令を使った場合は,GPUの場合より高速になる可能性があります。見積もりではElixirの同等プログラムと比べて13倍前後の速度向上を期待できそうです。
- GPUの場合は,データの転送に時間がかかっているので,データの転送量に比べて演算負荷が大きくなればなるほど,CPUよりGPUの方が有利になると思われます。
- CPUとGPUで適性を見極めて適切に負荷分散をすること,さらにCPUとGPUを並列実行することで,さらなるパフォーマンスを引き出せる可能性があります。
このあとすぐにNIF(Native Implemented Functions)を使って,ElixirのプログラムからOpenCLのプログラムを呼び出すことを考えたいのですが,その前に小休止として,AI/MLのデファクトスタンダードであるPythonとElixir,OpenCLの比較をしてみましょう。
おさらい〜 Elixir / Flow で書くロジスティック写像ベンチマーク
Elixirでロジスティック写像のベンチマークを書くとこんな感じです。
def calc(x, p, mu) do
rem(mu * x * (x + 1), p)
end
Flowとパイプライン演算子を使って次のように駆動します。
list
|> Flow.from_enumerable(stages: stages)
|> Flow.map(& calc(&1, p, mu))
|> Enum.to_list
実際にはロジスティック写像の関数を10回呼び出しました。
Python / NumPy で書くロジスティック写像ベンチマーク
では同じプログラムを Python / NumPy で書いてみましょう。
def logisticmap_calc(x, p, mu):
return np.mod(np.multiply(mu, np.multiply(x, np.add(x, 1))), p)
かっこが何重にも入れ子になっているので,パイプライン演算子で書きたくなりますね。
ベンチマーク全体は下記の通りです。
#-*- using:utf-8 -*-
import time
import numpy as np
def logisticmap_calc(x, p, mu):
return np.mod(np.multiply(mu, np.multiply(x, np.add(x, 1))), p)
def logisticmap_loopCalc(num, x, p, mu):
for i in range(1, 10, 1):
x = logisticmap_calc(x, p, mu)
return x
t1 = time.time()
x = np.array([i for i in range(1, int('0x2000000', 16), 1)])
#x = np.array([i for i in range(1, 10, 1)])
logisticmap_loopCalc(10, x, 6700417, 22)
t2 = time.time()
diff_time = t2 - t1
print(f"time: {diff_time} sec")
ちなみにNumPy同様の書き方でCUDA経由でNVIDIAのGPUを駆動できるCuPyというのがあります。OpenCL(GPU)のベンチマークと比較するならCuPyと比較したいところですが,今はまだ環境設定がうまくいっておらず試せていないので、後日に期待!
余談ですが,CuPyの環境設定はインストールしなければならないライブラリがたくさんあるので,なかなか大変ですね。できればワンコマンドでインストール可能なものであってほしいなと思います。ZEAMではできるだけ環境設定が容易なようにしたいなと思っています。
結果
ElixirとPythonを比較した結果は次の通りです。
Elixir(秒) | Elixir(秒) | Elixir(秒) | Python |
---|---|---|---|
1並列ループ | 8並列ループ | 8並列インライン展開 | NumPy使用 |
52.795620 | 12.664873 | 11.308742 | 17.83559 |
並列処理するElixirの方がNumPyを使用するPythonより同条件のループで1.40倍速いです。NumPyの処理の本体はC言語で書かれているということですから,これはかなり健闘していると言えます。ちなみにインライン展開すると1.58倍速いです。
今までの Elixir,OpenCL の実行結果に Python の結果も足しました。
Elixir(秒) | Elixir(秒) | Elixir(秒) | C言語(秒) | OpenCL(秒) | OpenCL(秒) | OpenCL(秒) | OpenCL(秒) | Python |
---|---|---|---|---|---|---|---|---|
1並列ループ | 8並列ループ | 8並列インライン展開 | 1並列ループ | 8並列ループ | 8並列インライン展開 | GPUループ | GPUインライン展開 | NumPy使用 |
52.795620 | 12.664873 | 11.308742 | 4.232451 | 1.496656 | 1.483530 | 1.072500 | 1.047713 | 17.83559 |
OpenCL(GPU)はPythonよりも,16.6倍速いです。インライン展開すると17.0倍速いです。GPUを利用するCuPyと比較したいところですね。
私たちが開発を進めようとしているZEAMでは Elixir / Flow で書かれたプログラムをGPU駆動して実行できるようにしようとしています。実はさらに,AI/MLライブラリをElixirに実装するという研究も進めているところです。
もしこれらが実用になった暁には,PythonからElixirに移行することでAI/ML処理の実行速度が大幅に向上する可能性があると言えます。
おわりに
- Elixir / Flow は Python / NumPy より1.5倍前後速いです。
- OpenCL(GPU) は Python / NumPy より17倍前後速いです。
- 今後,研究・開発が進んだ暁には,PythonからElixirに移行することで,AI/ML処理の実行速度が大幅に向上する可能性があります。
次はElixirからRustlerで呼び出すようにしてみたいと思います。お楽しみに!
明日は @takasehideki さんの「ElixirでIoT#1.1:ラズパイへのErlang/Elixir環境の構築」です! こちらもお楽しみに!
満員御礼!Elixir MeetUpを6月末に開催します
※応募多数により、増枠しました
「fukuoka.ex#11:DB/データサイエンスにコネクトするElixir」を6/22(金)19時に開催します
私も現在連載中のElixirのGPU駆動について発表します!