[[PageNavi(NavigationList)]]
====== TBBによるメディアンフィルタプログラムの並列化 ======
それでは、TBBを利用してメディアンフィルタプログラムを並列化してみよう。まず、必要なヘッダファイルをインクルードする。TBBは機能ごとにヘッダファイルも分割されており、必要なものを個別にインクルードする形になる。今回はタスクスケジューラおよび「parallel_for」アルゴリズム、そしてデータ範囲を指定するイテレータは2次元の範囲を指定できる「blocked_range2d」を使用するため、それぞれに対応するヘッダファイルをインクルードする。また、TBBは「tbb」という名前空間を使用するので、そちらを省略して利用できるように指定しておく。
{{{
#include "tbb/task_scheduler_init.h"
#include "tbb/parallel_for.h"
#include "tbb/blocked_range2d.h"
/* tbb名前空間をインポート */
using namespace tbb;
}}}
次に、並列処理を行う関数を関数オブジェクトとして定義する。ここでは、「!!ApplyMedFilter」というクラスとして定義している。関数に与える引数はクラスのパブリック変数として定義し、コンストラクタで初期化する形とした。そのほか、処理に必要とする関数もこのクラスのprivateメンバ関数として定義しておく。
{{{
class !ApplyMedFilter {
public:
const image_buffer* buf_in;
const image_buffer* buf_out;
const image_buffer* buf_tmp;
!ApplyMedFilter(const image_buffer* in,
const image_buffer* out,
const image_buffer* tmp) {
buf_in = in;
buf_out = out;
buf_tmp = tmp;
}
private:
/* 使用する作業関数を定義 */
:
:
public:
/*
* operator(): 並列実行する処理
*
* const blocked_range2dsize_t r: 処理する範囲
*/
void operator() (const blocked_range2dint r) const {
int x, y, i, j, dx, dy;
JSAMPLE* tmp_array[9];
for (y = r.rows().begin(); y != r.rows().end(); y++) {
for (x = r.cols().begin(); x != r.cols().end(); x++) {
/* 対象ピクセルとその近傍8ピクセルのアドレスをソートバッファにコピー*/
for (j = 0; j 3; j++) {
for (i = 0; i 3; i++) {
tmp_array[3*j + i] = (buf_tmp-array[y-1+j][x-1+i]);
}
}
/* 9ピクセルをソート */
med_sort(tmp_array, 9);
/* 中央値となるピクセルの相対位置を取得 */
dy = get_med_position_y(tmp_array, x, y);
dx = get_med_position_x(tmp_array, x, y, dy);
/* 出力バッファの対象ピクセルの値を中央値となるピクセルの値に設定 */
for (i = 0; i 3; i++) {
buf_out-array[y][3*x + i] = buf_in-array[y+dy][3*(x+dx) + i];
}
}
}
}
};
}}}
処理を並列実行させる個所は次のようになる。まずタスクスケジューラを初期化し、関数オブジェクトおよび処理範囲を設定するイテレータを割り当てる。ここで使用しているblocked_range2dは2次元の範囲を指定できるイテレータで、ここではX=1からX=width-1、Y=1からY=height-1までの範囲を指定している。
最後に使用する処理アルゴリズム(ここではparallel_for)に処理を定義した関数オブジェクトと処理する範囲を指定したイテレータを与えて実行すれば良い。
{{{
task_scheduler_init init;
!ApplyMedFilter filter(buf_in, buf_out, buf_tmp);
/*
* blocked_range2dで処理範囲を指定する。
* 第1引数および第2引数で1次元目の開始値及び終了値を、
* 第4引数及び第5引数で2次元目の開始値および終了値を指定する。
* 第3引数および第6引数では1次元目および2次元目の分割の粒度を指定する
*/
blocked_range2dint range(1, height-1, 10000, 1, width-1, 10000);
parallel_for(range, filter);
}}}
なお、このプログラムを実行した際にかかった時間は下記のとおりだ('''表6''')。
{{{ html
<h6>表6 TBBによる並列処理プログラムのパフォーマンス</h6>
<table class="wikitable" border="1">
<tr><th>プログラム</th><th>実行時間</th></tr>
<tr><td>シングルスレッド版(med_serial.c)</td><td>5553ミリ秒</td></tr>
<tr><td>TBB版(med_tbb.cpp)</td><td>3182ミリ秒</td></tr>
</table>
}}}
TBBを利用する場合も、環境に応じてスレッドが生成され、マルチコアCPUでより効率的な処理が可能である。また、並列化の粒度を柔軟に制御できるのも特徴である。
[[PageNavi(NavigationList)]]