ソフトウェア高速化の鍵は「並列化」:いま注目される並列化技術を知る 3ページ

並列処理の実装方法

 以上で述べてきたとおり、現在では並列処理がトレンドになりつつあり、プログラムを並列化する手法についても簡単な物から複雑なものまで様々なものが登場している。以下では、現在利用できる並列化の方法について紹介しておこう。

プロセス/スレッドを利用した実装

 並列処理の実装としてもっともプリミティブなものが、プロセス/スレッドを利用した実装である。これは、並列化したい処理を異なるプロセス/スレッドとして動作させる、というものだ。たとえばWindows環境では_beginthread()関数や_beginthreadex()関数、CreateThread()関数などを利用することで、特定の関数を別スレッドで実行できる。また、UNIX環境では主にサーバーアプリケーションなどにおいて、fork()関数を用いてプロセスを生成して処理を行わせる例が多いほか、POSIXスレッド(pthreads)という関数群を利用してマルチスレッド処理を行わせることができる(表1)。

表1 Windows/Linux/UNIX等で利用できる代表的なプロセス/スレッド処理関数/API
OS環境 プロセス/スレッド 利用できる関数
Windows スレッド生成 Cランタイムライブラリ _beginthread()、_beginthreadex()、
Windows API CreateThread()
MFC CWinThreadクラス、AfxBeginThread()
プロセス生成 CreateProcess()
UNIX/Linux スレッド生成(POSIXスレッド) pthread_create()
プロセス生成 fork()

 プロセス/スレッドを利用すると柔軟に処理を実装できる半面、共有データの管理やプロセス/スレッドごとの同期についても自前で実装する必要があるため、工数が増えるのが問題である。

 現代のマルチタスクOSはプロセス/スレッド同士が安全に情報をやりとりできる仕組みを備えているものの、このような処理は一般的に時間的コストが高く、また設計を間違えるとデッドロックなども発生する可能性がある。特に並列化されたプログラムはデバッグが難しく、「特定の状況でのみ問題が発生する」といったいやらしいトラブルが発生しやすい。

プロセスとスレッドの違い

 プロセスおよびスレッドは、どちらもCPUが処理を行う単位であるが、プロセスは個別にメモリ空間を持つのに対し、同じプロセスから作られたスレッドはメモリ空間を共有するというのが異なる点である。

 そのため、複数のプロセスを使って並列処理を行う場合、プロセス同士が通信を行い、共有するデータをやりとりする仕組みが必要となる。いっぽう複数のスレッドを使った並列処理の場合は、同一のメモリ空間をそれぞれのスレッドが使用するため、同じ変数を共有して利用でき、スレッド同士の通信が最小限で済むというメリットがある。また、プロセスの生成は一般に重い処理であることが多く、並列化によるオーバーヘッドが大きくなる傾向がある。そのため、一般的なアプリケーションで並列処理を行う場合はスレッドを利用する場合が多い。

OpenMPを利用した並列化

 スレッド/プロセスを明示的に利用して並列処理を実装する場合、並列して処理させたい個所を関数として実装する必要がある。しかし、一般のプログラムにおいては、並列化したい個所というのは特定のループやブロックのみである場合が多い。OpenMPは、プログラムのソースコード中に特定のプラグマを挿入することで並列化すべき個所を指定し、コンパイラに並列化を行わせるものである。

 たとえば次の例は、OpenMPを使用してforループを並列化する例である。

example_func1();

#pragma omp parallel for
for(i = 0; i < 100; i++ ) {
  example_func2(i);    /* 並列実行する処理 */
}

example_func3();

 「#pragma omp parallel for」プラグマは、このプラグマに続くforループを並列化するようコンパイラに指示するものだ。このように記述されたプログラムは、以下の図4のように関数example_func2()のみが並列実行され、それ以外の処理についてはシングルスレッドで実行される。

図4 OpenMPを使用した並列プログラム
図4 OpenMPを使用した並列プログラム