2016. 4. 3.
石立 喬

OpenMPとVisual C++による並列処理(6)

――― ヒストグラム作成は、OpenMPの効果が期待できない -----

 ヒストグラムは、画像を構成する各ピクセルのRGBなどの出現率をグラフ化したもので、デジカメや画像処理ソフトには、ヒストグラムを表示する機能が付いている。ヒストグラムを平坦化したり拡張したりすると、画像のコントラストを改善できる。
 画像の全ピクセルに対して処理を行うので、OpenMPによる並列化に向いていると思って試してみたが、不満足な結果になった。

ヒストグラムの作成と、その並列化
 ヒストグラムは、画像のコントラストを表すもので、画像の補正に有効である。ヒストグラムを採るには、画像の全ピクセルをスキャンして読み取り、そのピクセル値をR、G、B値に分解して、それぞれの値に対応した各カウンタをカウントアップする。この資料では、R、G、B値の他に、グレイスケール化した時の濃度Density(D)も計算して、Dについてのヒストグラムも付け加えてある。
 画像のピクセル数が大きいと、この処理は並列化が効果的である筈であると考えて、OpenMPの使用を試みた。

OpenMPによる実行時間の確認
 下記のユーザ関数を作成し、従来同様に、これらの関数に要する時間を測定する。
◎createHistogram関数
 ヒストグラム作成のためのピクセルスキャンを単純に並列化する。forループの前に、下記を記述するのみである。
   #pragma omp parallel for private(color, r, g, b、d)
reduction(+ : r_hist)なども使用しようとしたが、「reduction句の変数はスカラーでなくてはいけない」とコンパイラに注意された。
◎createHistogram_quad関数
 OpenMPに分割を任せるのではなく、forループを最初から4分割したのがこの関数である。
   #pragma omp sections private(color, r, g, b、d)を使用し、4分割したforループの頭に
   #pragma section
を付ける。
 以上の関数の他に、結果の確認のために、drawHistogram関数を用意してヒストグラムを表示する。

調査方法
 処理内容 ---- 分割なし、4分割
 ビルド構成 --- DebugとRelease
の組み合わせに対して、分割なしにはcreateHistogram関数を使用し、4分割にはcreateHistogram_quad関数を使用して100回繰り返し、その都度の実行時間を測定して表示する。これを、Visual C++のデフォルトと、OpenMPを使用した場合とについて調べる。

作成したプログラム
 ここでも「・・・による並列処理(4)」を基礎にしている。図1に示すように、プロジェクトの最初の部分で、ユーザ関数のプロトタイプを宣言し、図2に示すように、ヒストグラム格納用カウンタを宣言している。その他は従来と同じである。
 実行時間測定用の関数をUserProgramから呼び出す方法も、従来と変わらないので、説明は省略する。


図1 プロジェクトの最初の部分でユーザ関数を宣言する



図2 UserProgramの最初の部分で変数を宣言する


createHistogram関数
 「Visual C++ 2010 Expressを用いた易しい画像処理(4)」のcreateHistogram関数の一部を変更して(r、g、bの他に最大値vを加える代わりに、グレイ化後の濃度dを加える)、Win32用に書き直したもので、図1に示す。
 P_SIZEは、画像のWIDTH * HEIGHTを意味する定数で、307,200のかなり大きい値であるので、このforループをOpenMPで並列化する。ただし、図1は、VC++デフォルトの実行時間を確認する場合のプログラムで、#pragma文がコメントアウトされている。


図3 ヒストグラムを採る関数


drawHistogram関数
 ヒストグラム作成の実行時間の確認とは別に、結果の描画を一回だけ行うために、用意した関数である。図2はその内容で、上から、赤、緑、青、濃度の順に幅1ピクセルの矩形を並べて描画する。


図4 ヒストグラムを描画する関数


createHistogram関数でヒストグラムを取得した結果
 図3は、Release構成のVC++デフォルト(#pragma文をコメントアウトした)での結果の一例を示す。図4はOpenMPを使用した場合で、VC++デフォルトの場合よりも実行時間が長くなっている。 得られたヒストグラムの形状には差異がないので、OpenMPは正常に動作していることが分かる。


図5 Release構成のVC++デフォルトで得られたヒストグラムと、実行時間の一例



図6 Release構成のOpenMP使用で得られた結果の一例、VC++デフォルトより遅い


createHistogram_quad関数
 あらかじめ、スレッド数4にforループを分割し、#pragma omp sectionsで個々にスレッドを割り当てたのがこの関数である。本来ならば、r_hist[r]などは、reduction句を用いたいところであるが、「reduction句の変数はスカラー演算型を指定しなくてはなりません」の注意が出てしまう。ただし、OpenMP ver. 3.0では、一部のFortranコンパイラで、reductionに配列を使用できる、との情報がある。



図7 forループを4分割してヒストグラムを採る関数


createHistogram_quad関数でヒストグラムを取得した結果
 図8は、forループをあらかじめ4分割してOpenMPを使用した場合で、分割しないでOpenMPを使用した図6の結果よりは若干時間が短縮されているが、まだVC++デフォルトには及ばない。具体的に、実行画面では示していないが、VC++デフォルトで4分割すると、かえって悪くなった(表1参照)。


図8 Release構成での4分割OpenMP使用での結果の一例、VC++デフォルトよりは遅い


OpenMPによる実行時間短縮の確認の結果
 結果をまとめると表1のようになり、以下のことが分かる。
 1)Debug構成では、OpenMPを使用しても、VC++デフォルトと変わらず、OpenMPは不必要である。
 2)Release構成では、OpenMPを使用すると、VC++デフォルトより遅くなり、OpenMPを使用すべきでない。
 3)4分割ループにすると、Release構成でのOpenMP使用以外は、かえって大幅に遅くなる。
 4)Release構成で、VC++デフォルトのままでヒストグラムを採るのが一番速い。

表1 実行時間のまとめ(100回の平均値)



結 論
 画像のヒストグラムの取得には、VC++の持つデフォルトの並列化機能で十分で、ループをあらかじめスレッド数に合わせ手分割したり、OpenMPを使用したりすると、かえって動作速度が遅くなることが分かった。
 理由としては、ヒストグラムを格納するカウンタ配列に対してreduction演算の指定ができず、並列化できないので、そこへのアクセスに競合が生じ、待ち時間が増えるためではないかと想像する。






「Visual C++の勉強部屋」(目次)