早くエンジニアになりたい

masatany's memorandum

Elixirでフィボナッチ数を計算してみる

このごろ流行のruby風味の関数型言語「Elixir」でフィボナッチ数を計算してみました。
そして、これまた、このごろ流行の関数型言語Rustで実装したフィボナッチ数を求めるプログラムを呼び出して、速度比較をしてみました。 とくに意味はありません。

環境

  • マシン

    • Microsoft Windows 10 Home
    • Intel64 Family 6 Model 78 Stepping 3 GenuineIntel ~2300 Mhz
    • 3,935 MB
  • Elixr

    • Erlang/OTP 18 [erts-7.2.1] [64-bit] [smp:4:4] [async-threads:10]
    • Elixir 1.3.1
  • Rust

    • rustc 1.9.0 (e4e8b6668 2016-05-18)
    • cargo 0.10.0-nightly (10ddd7d 2016-04-08)

検証したソースコード

どちらの言語もまだまだ初学者レベルなので、細かいところがおかしい場合はご指摘願います。 また、「こうした方が早くなる」等のアドバイスも非常に助かりますので、お時間があればご教示願います。

fibonacci.ex

defmodule Fibonacci do
  def start do
    [num_string|_] = System.argv
    num = String.to_integer(num_string)
    exec_cmd = "lib/rust_fib.exe " <> num_string
    IO.puts "Elixir fibonacci"
    {result_elixir, :ok} = :timer.tc(Fibonacci, :fibonacci_elixir, [num])
    IO.puts "#{result_elixir/1000}ms"
    IO.puts "Rust   fibonacci"
    {result_rust, :ok} = :timer.tc(Fibonacci, :fibonacci_rust, [exec_cmd])
    IO.puts "#{result_rust/1000}ms"
  end

  # Elixirのみで計算する場合
  def fibonacci_elixir(num) do
    IO.puts fibonacci(num)
  end
  
  def fibonacci 0 do 0 end
  def fibonacci 1 do 1 end
  def fibonacci(num) when (is_integer num) and (num > 0) do
    fibonacci(num - 1) + fibonacci(num - 2)
  end


  # Rustプログラムを呼び出す場合
  def fibonacci_rust(exec_cmd) do
    port = Port.open({:spawn, exec_cmd}, [:binary])
    Port.command(port, "")

    receive do
      {^port, {:data, result}} -> IO.puts(String.trim(result))
    end
  end
end

Fibonacci.start

rust_fib.rs

use std::env;

fn calc_fibonacci(n: u32) -> u32 {
    if n <= 1 {
        n
    } else {
        calc_fibonacci(n - 1) + calc_fibonacci(n - 2)
    }
}

fn fibonacci(mut argv: env::Args) -> Result<u32, String> {
    let arg1 = try!(argv.nth(1).ok_or("数字を1つ指定してください。".to_owned()));
    let n = try!(arg1.parse::<u32>().map_err(|err| err.to_string()));
    Ok(calc_fibonacci(n))
}

fn main() {
    match fibonacci(env::args()) {
        Ok(n) => println!("{}", n),
        Err(err) => println!("{}", err),
    }
}

結果

f:id:k_masatany:20160731164719p:plain ※ ms単位にしたため、us単位の数値が消えて0msになっています。すみません。
※ 与える数値が低い範囲(0~25くらいまで)は速度の数値が安定しなかったので、何度か計測して頻度の高い数値を使っています。
※ 画像出力の際に表の縦線が消えました。

  • 0から26までは、Elixirの計算速度の方が早いようです。と言っても、Elixir+Rustが遅いのではなく、外部プログラムを呼び出して結果を受けるまでのオーバーヘッドが必ず入ってしまうので遅くなる感じですね。
  • 27で双方が肩を並べ、30から外部プログラムの恩恵を受けられるようになりました。
  • 45では、Elixir+Rustの方が10倍早い結果が出ています。

まとめ

  • Elixirは書きやすい。Rustは少し取っつきにくかったです。
  • Elixirが遅いときは、重い処理を簡潔な外部プログラムで実装してPortで呼び出すという手もある。
  • 外部プログラムの呼び出しに一定のコストがかかるので、Elixirの実行速度が比較的遅いからと言って、むやみやたらに外部プログラムを呼ぶものではないと感じました。