演算の並列実行でCPUコアを使い切るかどうかを測定

【追記】
[tatsuya6502 さん](http://qiita.com/hykw/items/c6aa5578655dd5833117#comment-fe6be6a8de8cfc707d00) に、コメント頂いた内容を踏まえ、追記・修正を行いました。また、ついでに、ちょっと気になったので[ElixirのRangeとErlangの:lists.seqの速度比較](http://qiita.com/hykw/items/95da21e66304f32a4019)もやってみました。

Elixir(= Erlang)の特長の一つに、子プロセスを生成して並列実行することで、各 CPU core を活用することができるという事があります。

各言語で0〜50,000,000までの数字を加算するロジックを書き、ベンチマークと htop による CPU 利用率を測定してみました。

# 環境
- [さくらVPSの4G](http://vps.sakura.ad.jp/specification/) (CPU 4 core, RAM:4GB)
- C言語: gcc 4.4.7
- elixir: 1.1.1(Erlang 18.2.1)
- python: 2.7.8
- php: 5.4.45
- ruby: 1.8.7(かなり古い...）

# 測定結果
![bench2.png](https://qiita-image-store.s3.amazonaws.com/0/16022/53e180ac-8531-02c1-5278-136c2f590b4d.png)

| 言語 | 時間（秒）
|:--|--:|
| C言語 | 0.1
| Elixir（再帰処理) | 0.9
| Elixir（:lists.seq) | 1.5
| Elixir（並列実行) | 2.6
| python(reduce) | 2.8
| python | 6.5
| Elixir（直列実行） | 7.1
| php | 11.5
| ruby | 22.0

rubyは古すぎなので例外として、reduce() を利用した python は C言語で実装されてるロジックを呼び出している（はず）なので、1 core しか使ってないくせに、かなり早いです。

htop による CPU 利用状況はこんな感じです（1 core 系の言語はどれも同じなので php だけ）。Elixir で spawn した場合、各coreを利用しているのがわかります。

## php
![php.png](https://qiita-image-store.s3.amazonaws.com/0/16022/d5078b81-2158-0b89-d5a2-2fd4eb5f8ab2.png)

## Elixir（直列実行）
![elixir1.png](https://qiita-image-store.s3.amazonaws.com/0/16022/dcc613a8-9f32-383a-2423-4d2e302ab8e7.png)

## Elixir（直列実行）
![elixir2.png](https://qiita-image-store.s3.amazonaws.com/0/16022/c31e4f76-45e6-c9cb-acad-7bd201a94636.png)

# ソースコード
各言語のソースコードは [GitHub](https://github.com/hykw/qiita-elixir-cpucore) にあります。Elixirの並列実行版だけ、下記に転記しておきます。

```elixir:elixir_concurrent.exs
defmodule Sum do
  def calc(from, to) do
    Enum.reduce(from..to-1, fn(x, acc) -> x + acc end)
  end
end

0..5000-1
|> Enum.map(&(Task.async(fn -> Sum.calc(10000*&1, 10000*(&1+1)) end)))
|> Enum.map(&(Task.await/1))
|> Enum.reduce(fn (x, acc) -> x+acc end)
|> IO.puts
```

# 【追試】
追試したソースコードは [GitHub](https://github.com/hykw/qiita-elixir-cpucore/commented/) にあります。

## Range を :lists.seq/2 に置き換え
速度に差異無し。Enum.map/2 内部の reduce() が呼ばれた時点で両者はほぼ同じ状態になるので当たり前か？

```elixir:sum1.exs
efmodule Sum do
  def calc(from, to) do
    Enum.reduce(from..to-1, fn(x, acc) -> x + acc end)
  end
end

:lists.seq(0, 5000-1)
|> Enum.map(&(Task.async(fn -> Sum.calc(10000*&1, 10000*(&1+1)) end)))
|> Enum.map(&(Task.await/1))
|> Enum.reduce(fn (x, acc) -> x+acc end)
|> IO.puts
```

## Enum.reduce/2 を :lists.sum/1 に置き換え
約40%の速度向上になりました。

```elixir:sum2.exs
defmodule Sum do
  def sum_seq(from, to) do
    :lists.seq(from, to - 1) |> :lists.sum
  end
end

0..5000-1
|> Enum.map(&(Task.async(fn -> Sum.sum_seq(10000*&1, 10000*(&1+1)) end)))
|> Enum.map(&(Task.await/1))
|> Enum.reduce(fn (x, acc) -> x+acc end)
|> IO.puts
```

## リスト処理を無くして再帰処理
約70%の速度向上になりました。

```elixir:sum3.exs
defmodule Sum do

  def sum_loop(from, to) do
    sum_loop1(from, to, 0)
  end

  defp sum_loop1(to, to, acc), do: acc

  defp sum_loop1(from, to, acc) do
    sum_loop1(from + 1, to, acc + from)
  end

end

0..5000-1
|> Enum.map(&(Task.async(fn -> Sum.sum_loop(10000*&1, 10000*(&1+1)) end)))
|> Enum.map(&(Task.await/1))
|> Enum.reduce(fn (x, acc) -> x+acc end)
|> IO.puts
```

# まとめ
- 簡単なソースコードで CPU core を有効に利用できるElixir(Erlang) すごい。
- C言語の圧倒的な演算速度を見ちゃうと、スクリプト言語は演算系の処理には向いてないことが良く分かる（確か、Erlang の作者も強みはそこじゃない、みたいなことを言っていた）。Erlangをスクリプト言語に入れていいかどうかはともかく。
- リスト処理はわりとコスト高なので、パフォーマンスが問題になった時には真っ先にチューニングしてみるといい。