スクリプト言語のマルチプロセス処理まとめ

# 概要
並列・並行処理、関数型、リアクティブなど、スケーラビリティを意識したプログラミングや言語が最近注目を集めている印象。そこでWeb開発でよく使われれるスクリプト言語3種を使ったマルチプロセス処理の基本的な書き方についてまとめ


# 環境

## 言語
- Ruby 2.2
- Python 3.1
- PHP 5.6

## 実行環境
- Vagrant
- CentOS6.4
- memory 1024MB
- CPU core 4

```rb:Vagrantfile
config.vm.provider :virtualbox do |vb|
    vb.customize ["modifyvm", :id, "--memory", "1024", "--cpus", "4", "--ioapic", "on"]
  end
```

## CPU確認コマンド
```sh
top -d1
# 1を押して全CPUを表示
```

# Ruby

## サンプルコード

```rb
require "digest/md5"
require "securerandom"
require 'benchmark'

pcount = 4

def single(pcount)
    (pcount - 1).times do
        arr = []
        100000.times do
            arr << Digest::MD5.digest(SecureRandom.uuid)
        end
    end
end

def multi(pcount)
    pids = []
    (pcount - 1).times do
        # プロセス生成
        pids << fork do
            arr = []
            100000.times do
                arr << Digest::MD5.digest(SecureRandom.uuid)
            end
        end
    end

    Process.waitall

end

single_time = Benchmark.realtime do
    single(pcount)
end
print single_time.to_s() + "\n"

multi_time = Benchmark.realtime do
    multi(pcount)
end
print multi_time.to_s() + "\n"

```

## CPU稼働率

### シングルプロセス

```
Cpu0  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
```

### マルチプロセス

```
Cpu0  : 99.0%us,  1.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  : 99.0%us,  1.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 99.0%us,  1.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
```

## 結果
- シングルプロセス：4.066100899999924
- マルチプロセス：1.2609145170000602

# Python

## サンプルコード

```py
import multiprocessing as mp
import hashlib
import uuid
import time
import os

def create_hash(res=[]):
    print('created: ' + str(os.getpid()))
    for _ in range(100000):
        res.append(hashlib.md5(str(uuid.uuid4()).encode('utf8')).hexdigest())
    return res

def single(pcount):
    start_time = time.time()
    print('parent: ' + str(os.getpid()))
    res = []
    for _ in range(pcount):
        res = create_hash(res)
    end_time = time.time()
    print('all finished : ' + str(end_time - start_time))

def multi(pcount):
    start_time = time.time()
    print('parent: ' + str(os.getpid()))
    processes = []
    for _ in range(pcount):
        processes.append(mp.Process(target=create_hash, args=()))

    for process in processes:
        process.start()

    for process in processes:
        process.join()

    end_time = time.time()
    print('all finished : ' + str(end_time - start_time))

if __name__ == '__main__':
    pcount = 4
    single(pcount)
    multi(pcount)
```

## CPU稼働率

### シンプルプロセス

```
Cpu0  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
```

### マルチプロセス

```
Cpu0  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  : 99.0%us,  1.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st

```

## 結果
- シングルプロセス：11.234153509140015
- マルチプロセス：3.374380588531494



# PHP

## サンプルコード

```php
// 親プロセス + 子プロセス = 4プロセス
$pcount = 3;

function single($pcount) {
    $time_start = microtime(true);
    $res = [];
    echo 'parent : '.getmypid()."\n";

    foreach(range(0, $pcount - 1) as $i) {
        echo 'created : '.getmypid()."\n";

        foreach(range(0, 100000) as $ii) {
            $res[] = md5(uniqid(mt_rand(), true));
        }
    }

    $time_end = microtime(true);
    $time = $time_end - $time_start;

    echo "all finished : $time\n";
}

function multi($pcount) {
    $processes = [];
    $time_start = microtime(true);
    echo 'parent : '.getmypid()."\n";

    foreach(range(0, $pcount - 1) as $p) {
        // ここで親プロセスと子プロセスに分かれる。
        // 親プロセスは子プロセスの生成に成功したら、そのプロセスのPID_IDを
        // 失敗したら-1を取得
        // 子プロセスは0を取得
        $pid = pcntl_fork();

        // 子プロセスの生成に失敗
        if ($pid === -1) {
            echo 'Failed process fork';
            exit;
        }

        // 子プロセスの処理
        if ($pid === 0) {
            $res = [];

            foreach(range(0, 100000) as $i) {
                $res[] = md5(uniqid(mt_rand(), true));
            }
            echo 'created : '.getmypid()."\n";
            exit;
        }

        // 親プロセスの処理
        $processes[] = $pid;
    }

    foreach($processes as $process) {
        pcntl_waitpid($process, $status);
    }

    $time_end = microtime(true);
    $time = $time_end - $time_start;

    echo "all finished : $time\n";
}

single($pcount);
multi($pcount);
```

## CPU稼働率

### シングルプロセス
```
Cpu0  :100.0%us,  0.0%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  :  0.0%us,  0.0%sy,  0.0%ni,100.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  :  0.0%us,  1.0%sy,  0.0%ni, 99.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  :  0.0%us,  1.0%sy,  0.0%ni, 99.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
```

### マルチプロセス
```
Cpu0  :  1.0%us,  0.0%sy,  0.0%ni, 99.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu1  : 99.0%us,  0.0%sy,  0.0%ni,  1.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu2  : 97.0%us,  1.0%sy,  0.0%ni,  2.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Cpu3  : 96.1%us,  1.0%sy,  0.0%ni,  2.0%id,  0.0%wa,  0.0%hi,  1.0%si,  0.0%st
```

## 結果

- シングルプロセス：3.6972289085388
- マルチプロセス：1.7085299491882

# まとめ
- スクリプト言語でもマルチプロセスで処理する方法はある。
- OSがマルチコアでなければマルチプロセス処理にしても高速化されなかった（あたりまえ）。
- 各CPUコア数の環境下で、最も処理が高速化されるのはCPUコア数と同じプロセス数の時で、それ以上プロセスを増やしても高速化されない（むしろ遅くなる）。
- マルチプロセスではプロセス毎にメモリが割り当てられる。
- [Elixir と Reactive System に関する考察](http://qiita.com/naoya@github/items/8a626a8b6c3a46b9d21b)や[並行処理に有効なメッセージパッシング](http://プログラマが知るべき97のこと.com/エッセイ/並行処理に有効なメッセージパッシング)にあるようなアーキテクチャを意識した実装をするには、Rubyは[process](https://github.com/grosser/parallel)、Pythonは[gevent](http://methane.github.io/gevent-tutorial-ja/)を使うのがよさそうなので別途調査。PHPは[pthreads](http://www.php.net/manual/ja/book.pthreads.php)ってモジュールがあるが、もともとPHPはシングルスレッドが前提になっているので無理やり動かすくらいなら他の言語を選択したほうがよさそう。