読者です 読者をやめる 読者になる 読者になる

OpenMPとウェブアプリケーションの組み合わせについて

PerlMagick が OpenMP 有効だと高負荷になる件 :: drk7jp

を見て、僕も自分が書いているライブラリで、OpenMPを使いまくっていて、将来的に同じことを言われる不安があるので、うまく付き合うためのノウハウを書いておきます。

まずOpenMPとは何かというと、並列コンピューティング環境を利用するために用いられる標準化された基盤です(Wikipediaより)。これを使うとC言語のfor文を並列化したりが簡単にできるので、マルチコアのCPUを使っている場合に、うまくいけばコア数倍の速度で処理できるようになります。1000msかかってた処理が4コアのCPUだと250msで処理できるようになった! やったね! というわけ。

しかし、gccVC++でのOpenMPの並列化for文はデフォルトではプロセス毎にコアを全部使って処理するので、これをマルチプロセスと組み合わせると、プロセスごとにコア数分のスレッドが生成されるため、システム全体では、OSから見えるプロセッサ数以上のスレッドが生成されて各プロセスが処理を取り合うことになり逆に性能が落ちます。
こういう場合でもうまく動かすために、環境変数OMP_NUM_THREADSというものがあって、この変数にスレッド数を設定することでOpenMPが生成するデフォルトのスレッド数を変更できます。なので、

同時に立ち上がるプロセス数 * OMP_NUM_THREADS = OSから見えるプロセッサ数(普通はCPUのコア数)

とすれば最高の性能が出ると思います。設定方法はサーバーソフトウェアによって違うので書きませんが、プロセス数の制限と環境変数を設定する方法は大体あると思います。(またこれは単純な数値計算だけの場合で、長いI/Oが入る一般的な処理を含む場合はOSから見えるプロセッサ数より多いほうが性能が出る場合があります)

これを前提として、目的によって、同時に立ち上がるプロセス数を多く取るか、OMP_NUM_THREADSを多く取るかを選ぶことになります。

たとえば、アクセスは少ないけどすごく重い処理をするので、できるだけ速くレスポンスを返したい場合は、同時に立ち上がるプロセス数を1にしてOMP_NUM_THREADSをCPUのコア数と同じにします。これが、1リクエストについて1番短い時間でレスポンスを返せる設定です。ただ、これだと1リクエストずつ順番に処理することになるので、同時に何人かアクセスする環境では、待ち時間が長い人が出てきます。そういう場合は、同時に立ち上がるプロセス数を増やして、その分OMP_NUM_THREADSを減らします。プロセス数が2の場合は、OMP_NUM_THREADSが1/2になり、同時に2つのリクエストを処理できるようになる代わりに、1リクエストあたりにかかる時間が2倍になる可能性があります。OMP_NUM_THREADS=1がOpenMPが無効なのと同じ状態です(ただ、並列化の処理を分けて書いている場合はオーバーヘッドがあるので、ずっと1にする気なら無効にしたほうがいいです)。

ウェブはたくさんの人がアクセスするものですから、ほとんどの場合は無効でいいと思いますが、スーパークソ重い処理をするので、できるだけ速くレスポンスを返したくて、プロセス数でなくハードウェアの台数で並列化できる場合は、OpenMPを有効にしてプロセス数少なめでスレッド数と台数を増やしたほうが速く処理できてうれしいと思います。

ちょっと面倒ですけど、ただ飯の時代は終わった。

追記

OpenMP3.0だとOMP_THREAD_LIMITという環境変数があるらしく、使える場合はこっちを使ったほうが適切です。OMP_NUM_THREADSはプログラム中でスレッド数を指定している場合はそっちの優先度が高いので制限できない。
あと自分のライブラリだとなぜか環境変数の設定が効かなくて、oreore_procs()で並列数を決めるようにして、この中で自分で環境変数を読んだりしているので、なにか別の問題があるのかもしれないです。