シリーズとしてはElixirありきでrust( rustler )を使う人をメインに据えています.
|> 「Elixir + Rustlerでベクトル演算を高速化しよう〜Rustler初級者編 1 〜」
※この記事は「Elixir + Rustlerでベクトル演算を高速化しよう」の「よりみち」的な立ち位置です.
はじめに
Elixirでベクトル演算を扱うならlist
もしくはtuple
が必要です.
※ちなみに私の投稿記事では一貫してlist
を使っています.
([前回の記事](Elixir + Rustlerでベクトル演算を高速化しよう〜Rustler初級者編 1 〜)では,rustの方でtupleにしましたが)
となると必然的にlist
作成を高速化したくなります.Enum.to_list
が遅いって話は聞き逃せません.
これを機にlist
のnew
とzeros
関数を自作して速度を測定してみました.
開発環境
- OTP-21.0.9
- Elixir(1.7.3)
- rustler(0.18.0)
- rust(1.28.0)
コード
以下が使用したコードです.
※内積計算の部分は省いています
@index 100_000_000
で定数指定しています.
defmodule NifExample do
use Rustler, otp_app: :phx_rust, crate: :example
@index 100_000_000
# func with nif
def new(_a), do: exit(:nif_not_loaded)
def zeros(_a), do: exit(:nif_not_loaded)
# func for list
def new_ex(e1, e2) when e1 > e2, do: []
def new_ex(e1, e2) do
[e1] ++ new_ex(e1+1, e2)
end
def zeros_ex(n) when n < 1, do: []
def zeros_ex(n) do
[0] ++ zeros_ex(n-1)
end
def new_list_benchmark do
:timer.tc( fn ->
1..@index
|> Enum.to_list
end)
|>elem(0)
|>Kernel./(1_000_000)
end
def new_list_benchmark1 do
:timer.tc( fn ->
new_ex(1, @index)
end)
|>elem(0)
|>Kernel./(1_000_000)
end
def new_list_benchmark2 do
:timer.tc( fn ->
new(@index)
end)
|>elem(0)
|>Kernel./(1_000_000)
end
def zeros_benchmark do
:timer.tc( fn ->
List.duplicate(0, @index)
end)
|>elem(0)
|>Kernel./(1_000_000)
end
def zeros_benchmark1 do
:timer.tc( fn ->
zeros_ex(@index)
end)
|>elem(0)
|>Kernel./(1_000_000)
end
def zeros_benchmark2 do
:timer.tc( fn ->
zeros(@index)
end)
|>elem(0)
|>Kernel./(1_000_000)
end
end
実行
new
指定した数までlist
を作成する
Elixir enum | Elixir reursive | rust | |
---|---|---|---|
実行時間(一千万) | 1.482213 | 0.593355 | 0.240299 |
倍率 | (1.00) | 2.49 | 6.16 |
実行時間(一億) | 13.485997 | 6.70341 | 4.409689 |
倍率 | (1.00) | 2.01 | 3.05 |
Enum.to_list
と比較すると,自前の再帰呼び出しでも速度が上回っていますね.
zeros
指定した数だけ要素0
をもつlist
を返す
List.duplicate | Elixir recursive | rust | |
---|---|---|---|
実行時間(一千万) | 0.201562 | 0.55816 | 0.354279 |
倍率 | 2.76 | (1.00) | 1.57 |
実行時間(一億) | 1.8861 | 9.729135 | 6.261378 |
倍率 | 5.15 | (1.00) | 1.55 |
Enum
のzeros
に相当するものが見つからなかったので,Erlangの:lists.duplicate
の測定をしました.
rustより速いんですよね.ソースコードを見に行ったら, 私が自前のElixirで実装した方法とは違う方法で再帰呼び出ししてます.
組み込み型に関数があるんならラップするだけでいいので自前の関数必要なかったですね.
def zeros(num) do
List.duplicate(0, num)
end
ただlist
に対して++/2
を使用するとパフォーマンスが落ちることはわかりました.
ちゃんとhead
とtail
を使いましょう.
まとめ
- やっぱり
Enum.to_list
遅い -
list
のnew
はrustが速い- Erlangのソースコードを眺めたら
:lists.duplicate
が応用できそうな気がするので一旦保留
- Erlangのソースコードを眺めたら
-
zeros
,ones
なら実装は組み込み型関数の利用で容易.
今後の記事予定
メイン
- NIF非同期呼び出しの実装
- 並列処理の実装(CPU)
- 並列処理の実装(GPU)
- Elixirからrustに
range
(例 1..10_000_000 )を渡せるようにする関数to_range
の実装
よりみちの続き
- Erlangの関数を呼び出す vs Erlangと同じ挙動のコードをElixirを書いて呼び出す
- パフォーマンスの比較
- 移植性?の確認
- 可読性の比較