PHPで並列プログラミング
パラレルクエリの流れを確認する
「第1回:DBは習うより慣れよ!」(http://www.thinkit.co.jp/article/97/1/)では、サンプルコードにより簡単なパラレルクエリを体験していただきました。これを以下の流れに沿って、簡単におさらいしていきます。
1.あらかじめデータを細切れにする
2.処理を分割
3.手分けして同時に処理'
4.結果をまとめる
「1.あらかじめデータを細切れにする」は、サンプルのデータベースを作る段階で行っています。
サンプルでは、test0というテーブルをtest1とtest2に2分割しています。実際には、test1とtest2を作成してから、この2つをマージしてtest0を作りましたので、上記の流れとは順番が逆ですね。
このように、データを細切れにする実際の作業は、「先に全体像があって、それを細切れにする」のではなく、パラレルクエリを念頭に置きながら「分割した個々の部分を構築して、それをまとめ上げる」ことが多いです。いずれにしても、データを細切れにする作業は、「あらかじめ」準備段階で行っておくことです。
「2.処理を分割」は、特に行っていません。
強いて挙げるならば、図1の9行目の「array('test1', 'test2')」の部分でしょうか。test1とtest2に対してクエリを行えば良いことはこのサンプルでは自明ですが、どこにクエリを投げるべきかの判断が複雑な場合もあります。無駄にリソースを食いつぶさないため、必要十分な範囲にクエリを投げることが肝要です。
「3.手分けして同時に処理」は、並列処理の主要な部分です。
図1の9行目から12行目がそれにあたり、特に11行目がポイントです。コマンドラインの最後に「&」を付けることで、mysqlコマンドをバックグラウンドで実行しています。バックグラウンドで実行することで、mysqlコマンドを2つ同時に実行しています。
なお、2つのクエリの結果は、5行目で作成した一時ファイルに追記します。このサンプルでは、複数のプロセスの状態の管理を、一時ファイルで行っています。この実装方法はさまざまです。より簡略な方法としてはmemcacheなどで行う場合もありますし、よりカッチリやるならば管理用のデータベースを用いることもあります。
「4.結果をまとめる」は2ステップからなります。14、15行目は、「3.手分けして同時に処理」の処理がすべて完了しているかをチェックします。一時ファイルの行数が2行、すなわち、test1とtest2の両方の結果が一時ファイルに書き出されるまで待ちます。
17行目で、一時ファイルの内容を合計し、結果を表示しています。
なお、バッククォートで囲まれたコマンドが11行目などにありますが、これらは shell_exec関数と等価です。戻り値は、コマンドの出力を改行区切りした一連の文字列となります。17行目では一時ファイルの各行の和の計算を1行で記述しています。ファイルやテキスト処理はコマンドラインのほうが小回りが利く場合があるので、簡易なスクリプトを書く場合には重宝します。特にデバッグ用のコードを書く際に威力を発揮します。
ただし、あまり一般的ではないようなので、チームで開発する場合にはこのようなトリッキーなことはしないほうが無難でしょう。
1台でどこまでパフォーマンスが高まるのか?
さて、サンプルでは、1台のマシンでパラレルクエリを行うと、3割ほどパフォーマンスが改善することを体感していただきました。では、1台のマシンでどこまでパフォーマンスが上がるのでしょうか?本当のパラレルクエリは、複数のマシンで分散して処理するものなので、純粋に興味本位ですが実験してみました。その結果を図1に示します。
横軸が並列の度合い、縦軸がクエリにかかった時間です。10回計測した平均値を示しています。
こうしてみると、あるところで頭打ちになることがわかります。環境によって違うので大体の感覚ではありますが、仮に1人でマシンを占有できるとしても、並列度「4」が1つの限界であるということは、知っておいて損はなさそうです。
では、次に図1で紹介したサンプルコードを、より実用に耐えるように改良していきましょう。改良すべき点は多々ありますが、もっとも問題なのは、恐らく高負荷になると安定しないということです。