概要
並列・並行処理、関数型、リアクティブなど、スケーラビリティを意識したプログラミングや言語が最近注目を集めている印象。そこでWeb開発でよく使われれるスクリプト言語3種を使ったマルチプロセス処理の基本的な書き方についてまとめ
環境
言語
- Ruby 2.2
- Python 3.1
- PHP 5.6
実行環境
- Vagrant
- CentOS6.4
- memory 1024MB
- CPU core 4
Vagrantfile
config.vm.provider :virtualbox do |vb|
vb.customize ["modifyvm", :id, "--memory", "1024", "--cpus", "4", "--ioapic", "on"]
end
CPU確認コマンド
top -d1
# 1を押して全CPUを表示
Ruby
サンプルコード
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
サンプルコード
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
サンプルコード
// 親プロセス + 子プロセス = 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 に関する考察や並行処理に有効なメッセージパッシングにあるようなアーキテクチャを意識した実装をするには、Rubyはprocess、Pythonはgeventを使うのがよさそうなので別途調査。PHPはpthreadsってモジュールがあるが、もともとPHPはシングルスレッドが前提になっているので無理やり動かすくらいなら他の言語を選択したほうがよさそう。