LoginSignup
4
2

More than 5 years have passed since last update.

Elixir + Rustlerでベクトル演算を高速化しよう 〜初級者編 1.5〜

Posted at

シリーズとしてはElixirありきでrust( rustler )を使う人をメインに据えています.
|> 「Elixir + Rustlerでベクトル演算を高速化しよう〜Rustler初級者編 1 〜」 

※この記事は「Elixir + Rustlerでベクトル演算を高速化しよう」の「よりみち」的な立ち位置です.


はじめに

 Elixirでベクトル演算を扱うならlistもしくはtupleが必要です.
※ちなみに私の投稿記事では一貫してlistを使っています.
前回の記事では,rustの方でtupleにしましたが)

となると必然的にlist作成を高速化したくなります.Enum.to_listが遅いって話は聞き逃せません.

これを機にlistnewzeros関数を自作して速度を測定してみました.

開発環境

  • OTP-21.0.9
  • Elixir(1.7.3)
  • rustler(0.18.0)
  • rust(1.28.0)

コード

以下が使用したコードです.
※内積計算の部分は省いています

@index 100_000_000で定数指定しています.

example.ex
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

Enumzerosに相当するものが見つからなかったので,Erlangの:lists.duplicateの測定をしました.
rustより速いんですよね.ソースコードを見に行ったら, 私が自前のElixirで実装した方法とは違う方法で再帰呼び出ししてます.
組み込み型に関数があるんならラップするだけでいいので自前の関数必要なかったですね.

def zeros(num) do
  List.duplicate(0, num)
end

ただlistに対して++/2を使用するとパフォーマンスが落ちることはわかりました.
ちゃんとheadtailを使いましょう.

まとめ

  • やっぱりEnum.to_list遅い
  • listnewはrustが速い
    • Erlangのソースコードを眺めたら:lists.duplicateが応用できそうな気がするので一旦保留
  • zerosonesなら実装は組み込み型関数の利用で容易.

今後の記事予定

メイン

  1. NIF非同期呼び出しの実装
  2. 並列処理の実装(CPU)
  3. 並列処理の実装(GPU)
  4. Elixirからrustにrange(例 1..10_000_000 )を渡せるようにする関数to_rangeの実装

よりみちの続き

  • Erlangの関数を呼び出す vs Erlangと同じ挙動のコードをElixirを書いて呼び出す
    • パフォーマンスの比較
    • 移植性?の確認
    • 可読性の比較
4
2
0

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
4
2