Entries

OpenMPのラフなおさらい

○概要
 知らなかった。VC++ Expressでも普通にOpenMPが使えたなんて。

 ……自分でもアホなことを言っているとは分かっています。でも知らなかったんです。Professional以降じゃないと使えないとかそういったものだとてっきり勘違いしていました。
 というわけで、自分でちょっとだけ触ってみた時のまとめ。

※使用機種は毎度の通りノーパソからです。

○とりあえず並列実行させてみる
 ものは試し、まずはソースを書いてみましょう。

#include <iostream>
#include <omp.h>
int main(void){
//{}で囲まないと、すぐ次の文のみを並列化する。
//そして、(論理)コア数分だけ並列に処理(実行)される。
#pragma omp parallel
std::cout << "Hello, OpenMP World!" << std::endl;
return 0;
}

 ここで問題。これを実行すると、一体何が表示されるでしょうか?
 ……正解は、「"Hello, OpenMP World!"が現在の論理コア数分だけ表示される」です。要するに、「#pragma omp parallel」という文字列によって、その次の行({}で囲んだ場合はその間)の文が単純に複数のコアによって実行されてしまうということです。全く同じことを複数コアが実行してもまるで無意味なのは分かりますよね? つまり、如何にそれぞれのコアに仕事を降るかがとても重要なのです。
 そのために、スレッド数を自動or手動調節したり、#pragma omp parallel構文内でも一時的にシングルスレッド動作するように小細工したり、どのコアで実行しているかの振り番号を取得したり、それぞれのコアに別々の仕事を振って時々同期させたりといった作業をするための手段が色々用意されてはいますが……今回はそこまで詳しく解説しません。だって面白くないもん。と言うか、並列化でブンブン言わせる一番わかり易い例(つまりfor文)をとりあえず示せば問題ない気がするし。
 一応ソースコード中にコメントで注釈は入れますが、詳しくはググったりして対処をお願いします。


○応用ですよ、応用!
 とりあえず身近でよく使われる(プログラミング的に)もので、かつfor文を大量に回して使わないといけないものの一つとしては行列があります。今回は、「Intel Compilerより、やすーい」ことで定評があるPGI Compilerでの並列化サンプルに使われている行列積をプログラミングしてみます。


/*
CD /d F:\学習用\OpenMPの練習
cl 公開用.cpp /openmp /O2 /EHsc
*/

/* 行列積計算(OpenMPテスト) */
/* usage:examin.exe <ArraySize> <Threads> */
/* */

#include <cstdlib>
#include <ctime>
#include <iostream>
#include <sstream>
#include <omp.h>

int main(int argc, char* argv[]){
/* コマンドライン処理 */
//配列サイズ(自然数でないと終了)
int ArraySize;
std::stringstream ss;
if(argc < 2) return 1;
ss << argv[1];
ss >> ArraySize;
ss.clear();
if(ArraySize <= 0) return 1;
//並列実行時のスレッド数
//(未入力か0以下だと論理コア数にセット)
int Threads;
if(argc < 3){
Threads = omp_get_max_threads();
}else{
ss << argv[2];
ss >> Threads;
ss.clear();
if(Threads <= 0) Threads = omp_get_max_threads();
}

/* 配列を用意 */
//vectorでも添字アクセスはO(1)なのだが、あえてnewしてみた
double **a = new double*[ArraySize];
double **b = new double*[ArraySize];
double **c = new double*[ArraySize];
for(int i = 0; i < ArraySize; i++){
a[i] = new double[ArraySize];
b[i] = new double[ArraySize];
c[i] = new double[ArraySize];
}

/* 適当な初期値を代入 */
//そこまで質は要らないので、rand関数で[0,1]に初期化する
srand(static_cast<unsigned int>(time(NULL)));
for(int i = 0; i < ArraySize; i++){
for(int j = 0; j < ArraySize; j++){
a[i][j] = 1.0 * rand() / RAND_MAX;
b[i][j] = 1.0 * rand() / RAND_MAX;
}
}

/* 行列積を計算 */
double BeginTime, EndTime, DiffTime;
//omp_set_dynamic(0)でスレッド数動的調節を無効化
//(omp_set_dynamic({0以外})なら有効化)
omp_set_dynamic(0);
//omp_get_wtime()で、過去のある地点からのwall clock timeを返す。単位は秒。
/* ---------------<計算始め>--------------- */
//c[][]を初期化
#pragma omp parallel num_threads(Threads)
{
int i, j;
//並列実行時、内部の変数は「共有」される。
//private()内に変数を指定することで、
//その変数はスレッド毎に保存できる(混ざらない)。
#pragma omp for private(j)
for(i = 0; i < ArraySize; i++){
for(j = 0; j < ArraySize; j++){
c[i][j] = 0.0;
}
}
}
//積和計算
BeginTime = omp_get_wtime();
#pragma omp parallel num_threads(Threads)
{
int i, j, k;
#pragma omp for private(j, k)
for(i = 0; i < ArraySize; i++){
for(j = 0; j < ArraySize; j++){
for(k = 0; k < ArraySize; k++){
c[i][j] += a[i][k] * b[k][j];
}
}
}
}
EndTime = omp_get_wtime();
/* --------------<計算終わり>-------------- */
DiffTime = EndTime - BeginTime;

//結果表示
std::cout << "実行時間(" << Threads << "スレッド):" << DiffTime << "[s]\n";

//メモリを開放
for (int i= 0; i < ArraySize; i++){
delete[] a[i];
delete[] b[i];
delete[] c[i];
}
delete[] a;
delete[] b;
delete[] c;

return 0;
}

 使い方は、「第一引数に行列の一辺のサイズ、第二引数に生成するスレッド数」を自然数で指定すればOK。
 第一引数は略せないが、第二引数は略すか0にすると「スレッド数自動設定」の方にルーチンが切り替わる仕組み。ちなみにこの場合の「自動」とは、「論理コア数そのまま」という意味だったりするので注意。
 で、「/O2 /EHsc /openmp /arch:AVX /favor:INTEL64」VC2012で64bitコンパイルして引数を「1500 スレッド数」として各10回づつぶん回してみた結果がこちら。なぜ64bitかって? だって折角64bitOSなんだし、パフォーマンスが出る方で回したかったからね。
スレッド数平均標本分散
135.825260.027868316
223.112840.22243611
319.291870.02277736
419.939060.01776541
520.502860.026979774
619.694680.007061476
719.816150.007992972
819.753580.008842888

グラフ


 ……まあ、4コアとは言っても所詮HTTですので、3コア目以降の速度上昇が鈍るのは当たり前。むしろ、こんな愚直なコードでも速度がグンと上がるのは凄いと言えます。
 大した知識を要さずに速度アップ。一度試してはいかがかな?

○参考資料
「Discypus.jp ソフト/OpenMP」(http://discypus.jp/wiki/?%A5%BD%A5%D5%A5%C8%2FOpenMP)
「基礎から学ぶOpenMP:CodeZine」(http://codezine.jp/article/corner/349)
「OpenMP Tech Note」(http://rest-term.com/technote/index.php/OpenMP)
「OpenMP入門」(http://www.nag-j.co.jp/openMP/)

以上っ。


2013/07/23追記:
流石に実験結果がテキトーすぎたので再実験して更新した。
関連記事
スポンサーサイト
この記事にトラックバックする(FC2ブログユーザー)
http://ysrken.blog.fc2.com/tb.php/51-83cfad34

トラックバック

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

Appendix

プロフィール

YSR

Author:YSR
「YSR」「YSRKEN」「◆YSRKENkO6Y(~2013/08/25)」「◆YSRKEN.ceVZZ(2013/08/26~)」として活動しています。
プログラミングと艦これが趣味です。
プロフ画像はCrystalDiskInfoの水晶雫ちゃんです。
主な創作物についてはhttp://ysrken.blog.fc2.com/blog-entry-76.htmlをご覧ください。

カレンダー(月別)

08 ≪│2017/09│≫ 10
- - - - - 1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30

全記事表示リンク

全ての記事を表示する

QRコード

QR

総アクセス数

アクセス数

現在の閲覧者数: