シングルスレッドな言語の並列化・マルチプロセス化についての暫定的な理解をまとめる

 · 8 min read

たまにはまとまってない情報を書いてもいいじゃないかということで、表題の通り暫定版です。

まえおき

Nodejsのclusterモジュールのドキュメントを読んでいて、
「ほぉ、並列化って簡単にできるんだなぁ」と感じつつ、関連記事をいろいろ調べてみると、

  • 並列化すればスループットが上がる
  • マルチコアの場合は有用。CPUのコア数と同じにすると良い
  • ワーカーやアプリケーションサーバなどは横に並べとけ

的な記述が色々あり、違和感を感じました。
Goなどのマルチスレッドができる言語でやる”並列化”と私が調べているものは別物なのでは? と。

ということで、身近な頼れる方々へ聞いて調べて考えた結果の暫定的な理解を書き留めておきます。 理解に誤りがあったら指摘もらえると喜びます。

パフォーマンスチューニングやスレッドセーフであるための4条項とかそういう込み入った話ではなく、浅い話です。

用語の整理。多重化と並列化とマルチプロセス化は別物である

たとえば、Goで並列処理をする場合、goroutineを使用すると思います。
jsで複数の非同期処理を同時に行おうとした場合、Promise.allを使用すると思います。
Herokuの記事によると、Nodeでワーカーの並列度を最適化するには、throngなどのクラスタリングマネージャを使用して、マルチプロセス化したら良いと思う的なことが書かれています。

一体なにがどれにあたるんだ、違いがわからん と思っていたのですが、
JavaScript(Nodejs)はマルチコアな筐体で動作させたとしても、あくまで シングルスレッド な言語であるという点から整理すると、

  • 多重化
    • 1つのCPUを複数のタスクでシェアし、待ち時間を減らしマルチスレッドかのように振る舞うこと
    • jsの非同期処理や、PHPのcurl_multiはこれ
    • シングルコアで動くgoroutineもこれ
  • 並列化
    • 複数のCPUを使用し、それぞれに異なる計算をさせること
    • マルチコアで動くgoroutineはこれ
  • マルチプロセス
    • プログラム中のある処理ではなく、プロセス自体を横に並べること。多重化や並列化とは別枠というか、比べる対象ではない
    • PHP-FPMやNodeのclusterモジュールがこれ

と理解しました。 多重化と並列化は置いといて、マルチプロセスについてより詳細にまとめていきます。

マルチプロセスの数はいくつが良いのか

頼れるパイセンがアドバイスをくれた。
なんとなーくCPUのコア数と同じって理解だったのが、
LoadAverageという値がCPUのコア数と同じになるように調整すれば良いと判明。

なのでプロセスの数自体はCPUのコア数と必ずしも一致しない模様。

ロードアベレージって何

load averageとは ロードアベレージはシステム全体の負荷状況を表す指標。
「1CPUにおける単位時間あたりの実行待ちとディスクI/O待ちのプロセスの数」で表される。
システムのスループットを上げたい場合はロードアベレージを下げることを目標にする。 load averageを見てシステムの負荷を確認する – Qiita

この引用部分だけでは 低ければ低いほど良い ように見えますが、
CPUコア数よりLoadAverageが高い場合は処理待ちが発生しており、逆に低すぎるとCPUパワーを余らせていることになる

なのでCPUのコア数より高い場合は下がるように改善を。
低すぎる場合は、余ってるマシンパワーを活かすようにプロセス増やしたりCPU負荷が高いけど高速な処理に変えたりと性能改善が可能

マルチプロセスはどのレイヤが担うべきなのか

最後。Nodejsにはclusterやchild_processなどのモジュールが組込みモジュールとして提供されている。
これはもう「アプリケーションをマルチプロセス化して下さい」と言っているようなものなのではないか…?
Herokuもthrongという簡易クラスタリング用のライブラリを使った例を出していたりする。

phpにもpcntlというプロセス制御の拡張機能がある。 シングルスレッドの言語でもプロセスを並べれば、スレッドをロックするようなsleep関数等もプロセスマネージャが多重化してくれるので、他の処理を行える。 ただし、PHPにはPHP-FPMなどのプロセスマネージャもある。

実装は、たかだか15行程度のコードで可能。でも実装が必要で、メンテも必要。 これってプログラム言語のレイヤが担うべき責務なのか? ? 言語でやらずにプロセスマネージャを別途利用したほうが良いのか?? というのが最後の疑問。

ちなみにNodejsのサンプルコードはこちら:

const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // Fork workers.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('exit', (worker, code, signal) => {
    console.log(`worker ${worker.process.pid} died`);
  });
} else {
  // Workers can share any TCP connection
  // In this case it is an HTTP server
  http.createServer((req, res) => {
    res.writeHead(200);
    res.end('hello world\n');
  }).listen(8000);
}

会社のパイセンに聞いてみたところ、
「並列処理とマルチプロセスは違う、マルチプロセスをやりたいなら言語レベルではなく、より上位でプロセスマネージャを利用したほうが良い。
なぜなら、本気でクラスタリングしたいなら、Nodeのサンプルコードのような簡素な実装ではままならず、とても複雑な考慮や制御が必要になるのでコストとリスクが高すぎるから」

と回答を得た。
「え、これ実装しなきゃ早くならないの? ? 実装汚れるなぁ…」と不安に思っていたので、独自実装はすべきでないという同じ方向性で良かった。

現時点で理解したことは以上です。

NodejsPerformancePHP
© 2012-2021 Leko