PHPのマルチスレッドプログラミングを使ってシステム処理を爆速化するお話し
- 作者: 小川雄大,柄沢聡太郎,橋口誠
- 出版社/メーカー: 技術評論社
- 発売日: 2010/11/12
- メディア: 大型本
- 購入: 32人 クリック: 1,065回
- この商品を含むブログ (60件) を見る
Intro
会社に入社して5年が経ち、4月から新しい部署で働いています。最近はプログラミング言語の学習としてC++/JAVA/Perl/R言語、理論の勉強として機械学習をやっています。平行して少しずつ勉強しているのでblogの記事内容も多種多様になってきています(笑)。新しい事をやる時は一つの事に集中して勉強したいのですが、直近は業務で成果を残さないと相手にされないので学習がforkします。ということで強引な繋ですが今日はforkの話をします。業務で必要になったPHPの処理爆即化に向けてマルチスレッドプログラミングを試してみました。pcntlにより親プロセスから子プロセスを作成してforkさせます。出来たところまでの成果を以下にまとめました。
PHP: PCNTL - Manual
Source Build
pcntlというPHPのマルチスレッドプログラミングはdefaultでは使えないようです。phpソースをbuildする時に--enable-pcntlを付ける必要があります。以前にmcrypt関数を使う時にもオプションをつけて--with-mcrypt=/usr/local/libを付けたりlibmcryptをインストールしないといけないことがあったので、標準で使えるようにして欲しいですね。
<?php $pid = pcntl_fork(); if ($pid == -1) { die('fork できません'); } else if ($pid) { // 親プロセスの場合 echo "parent process \n"; pcntl_wait($status); // ゾンビプロセスから守る } else { // 子プロセスの場合 echo "child process \n"; }$ php pcntl.php Fatal error: Call to undefined function pcntl_fork()次にphpソース取得とコンパイル、インストール手順を書きます。
$ wget http://jp.php.net/get/php-5.4.3.tar.gz/from/this/mirror $ gunzip php-5.4.3.tar.gz $ tar xf php-5.4.3.tar $ cd php-5.4.3 $ ./configure --enable-pcntl $ make && sudo make install再度上のサンプルプログラムを実行します。うまくProcessがforkされたようです。
$ php pcntl.php child process parent process
Practice
Non Multi-threadとMulti-threadの処理ロジックの比較を行います。下ではsleep関数を使っていますがsleepが重たい処理と見なした例です。
Non Multi-thread
直列で処理を実行します。当然ながら30secほど時間がかかります。
<?php $t1 = microtime(true); sleep( 10 ); echo "Complete No1 \n"; sleep( 10 ); echo "Complete No2 \n"; sleep( 10 ); echo "Complete No3 \n"; $t2 = microtime(true); $process_time = $t2 - $t1; echo "Process time = " . $process_time . "\n";Complete No1 Complete No2 Complete No3 Process time = 30.010495901108Multi-thread
複数の子プロセスを発生させて処理を並列化させます。プロセスをforkしているのでsleepを3カ所に入れても1カ所に入れた場合と変わりないと思います。これにより重たい処理を並列化することが可能になりました。処理が爆速化されます。ただし気をつけないといけないのが親Processが子Process全て終わる事を確認してから先に処理を進めないと無限Loopする可能性があります。ここは要注意です。しっかりとしたテストをしてから導入するようにしましょう。
<?php $t1 = microtime(true); $pcount = 3; $pstack = array(); for($i=1;$i<=$pcount;$i++){ $pid = pcntl_fork(); if( $pid == -1 ) { die( 'fork できません' ); } else if ($pid) { // 親プロセスの場合 $pstack[$pid] = true; if( count( $pstack ) >= $pcount ) { unset( $pstack[ pcntl_waitpid( -1, $status, WUNTRACED ) ] ); } } else { sleep( 10 ); echo "Complete No$i\n"; exit(); //処理が終わったらexitする。 } } //先に処理が進んでしまうので待つ while( count( $pstack ) > 0 ) { unset( $pstack[ pcntl_waitpid( -1, $status, WUNTRACED ) ] ); } $t2 = microtime(true); $process_time = $t2 - $t1; echo "Process time = " . $process_time . "\n";Complete No1 Complete No2 Complete No3 Process time = 10.086293935776以下は駄目な例です。親プロセス処理が先に進んでしまい、子プロセスの終了とともに再度親プロセスの処理が実行されてしまいます。上で示したようにexitを使って子プロセスが終わったらそのプロセスを終了するような処理を入れても良いと思います。
<?php $t1 = microtime(true); $pcount = 3; $pstack = array(); for($i=1;$i<=$pcount;$i++){ $pid = pcntl_fork(); if( $pid == -1 ) { die( 'fork できません' ); } else if ($pid) { // 親プロセスの場合 $pstack[$pid] = true; if( count( $pstack ) >= $pcount ) { unset( $pstack[ pcntl_waitpid( -1, $status, WUNTRACED ) ] ); } } else { sleep( 10 ); echo "Complete No$i\n"; } } $t2 = microtime(true); $process_time = $t2 - $t1; echo "Process time = " . $process_time . "\n";Complete No1 Process time = 10.013136148453 Complete No2 Process time = 10.020040035248 Complete No3 Process time = 10.035511016846 Process time = 10.039574146271 Complete No2 Process time = 20.015119075775 Complete No3 Process time = 20.022233009338 Complete No3 Process time = 20.063854217529 Complete No3 Process time = 30.069846153259
High-load
マシンがどれぐらChildProcessを生成できるのかを試してみました。当然ながら通常の処理ではあり得ないようなProcessを生成しています。実行するプログラムは上のMulti-threadでpcoutを標準入力から取得するように修正して色々な値で試してみます。Memoryのスペックは次の通りです。
$ cat /proc/meminfo MemTotal: 767556 kB MemFree: 41368 kB Buffers: 24760 kB Cached: 303132 kB SwapCached: 0 kB Active: 396132 kB Inactive: 268276 kB HighTotal: 0 kB HighFree: 0 kB LowTotal: 767556 kB LowFree: 41368 kB SwapTotal: 786424 kB SwapFree: 786424 kB Dirty: 104 kB Writeback: 0 kB AnonPages: 336528 kB Mapped: 35700 kB Slab: 34288 kB PageTables: 9584 kB NFS_Unstable: 0 kB Bounce: 0 kB CommitLimit: 1170200 kB Committed_AS: 720716 kB VmallocTotal: 34359738367 kB VmallocUsed: 1244 kB VmallocChunk: 34359735835 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 Hugepagesize: 2048 kBpcout = 100
pcount=3に比べて2秒ほど遅くなりましたが、loadaverageも高くなる事なく処理がさばけています。100Prcessを12秒でさばいています。
$ php pcntl.php 100 > result.txt $ top top - 02:11:15 up 6 min, 2 users, load average: 0.08, 0.56, 0.33 Tasks: 209 total, 2 running, 207 sleeping, 0 stopped, 0 zombie Cpu(s): 1.0%us, 0.3%sy, 0.0%ni, 98.0%id, 0.0%wa, 0.0%hi, 0.7%si, 0.0%st Mem: 767556k total, 759340k used, 8216k free, 24520k buffers Swap: 786424k total, 0k used, 786424k free, 310164k cached $ cat result.txt (略) Complete No100 Complete No97 Complete No37 Complete No42 Complete No69 Process time = 12.1791908741pcount = 300
pcount=100に比べてloadaverageがだいぶ高くなりました。ですが許容範囲かと思っています。300Prcessを15秒でさばいています。
$ php pcntl.php 300 > result.txt $ top op - 02:35:37 up 4 min, 2 users, load average: 18.11, 5.24, 1.88 Tasks: 108 total, 2 running, 106 sleeping, 0 stopped, 0 zombie Cpu(s): 0.7%us, 0.7%sy, 0.0%ni, 98.1%id, 0.0%wa, 0.0%hi, 0.4%si, 0.0%st Mem: 767556k total, 675656k used, 91900k free, 23728k buffers Swap: 786424k total, 0k used, 786424k free, 258656k cached $ cat result.txt (略) Complete No202 Complete No212 Complete No218 Complete No224 Complete No235 Complete No239 Complete No257 Complete No268 Process time = 15.838124036789pcount = 1000
あり得ないforkの仕方ではあるとおもいますがpcout=1000ではload averageが急激に高くなりました。しかも処理が終わりきる前にProcess timeの結果が出力されてしまいました。しかしforkに失敗したprocessは一つもなかったようです。これ以上は試しません。
$ php pcntl.php 1000 > result.txt $top top - 02:21:38 up 17 min, 2 users, load average: 138.01, 33.80, 11.48 Tasks: 108 total, 2 running, 106 sleeping, 0 stopped, 0 zombie Cpu(s): 14.5%us, 46.6%sy, 0.0%ni, 31.8%id, 0.0%wa, 1.7%hi, 5.4%si, 0.0%st Mem: 767556k total, 488932k used, 278624k free, 10872k buffers Swap: 786424k total, 0k used, 786424k free, 74836k cached $ cat result.txt Complete No385 Process time = 32.895464897156 Complete No610 Complete No626 Complete No632 Complete No641 Complete No646 Complete No657
Other Example
Non Pcntl
APIとの通信効率をよくする実装例(1) curl_multi (Yahoo! JAPAN Tech Blog)
Pcntl以外にもPHPでmulti-threadプログラミングをよく利用します。例えば上の例がそれでWebAPIなど直列でたたくと時間がかかりそうな場合は並列で処理するとその分処理時間が短縮されます。APIの数が多いほど効果が発揮されると思います。