なが月・37日目

37日目

午前

並列プログラミング入門を再開した。まず#pragma ompの効果が理解できた。すなわち、#pragma omp ...という指示文は次行に来る単位コード(;で終わる文、または{}ブロック)が実行されている間有効であるということだ。そのため

//誤り、"critical {" という指示はない
#pragma omp critical {
...
}

// 正しい、criticalはブロック内だけ有効
#pragma omp critical
{
...
}

と書く必要がある。criticalとはそのブロックを実行するのが各時点でただ1スレッドになるよう制限するものである。そしてこれを踏まえると25日目のコードで2番目のドット積計算(reductionを設定しなかったために結果が狂った計算)を書き直すことができる。

int main(){
    ...
    // parallel w/o reduction
    std::cout << "===parallel with no reduction===" << "\n";
#pragma omp parallel for
    for (int i = 0; i < 8; ++i) {
#pragma omp critical
        {
        dotProduct += a[i] * b[i];
        printf("adding: %d, dotProduct is now: %d\n", a[i] * b[i], dotProduct);
        }
    }
    std::cout << dotProduct << "\n";

これによって得られる出力は

===parallel with no reduction===
adding: 4, dotProduct is now: 4
adding: 36, dotProduct is now: 40
adding: 1, dotProduct is now: 41
adding: 25, dotProduct is now: 66
adding: 49, dotProduct is now: 115
adding: 9, dotProduct is now: 124
adding: 64, dotProduct is now: 188
adding: 16, dotProduct is now: 204
204
===parallel with reduction===

であり、めいめい担当するfor範囲の処理を行おうとするもののdotProductの現在の値を見て加算を行うことができるのは早いもの勝ちで一度に1スレッドになっていることが分かる。この書き方をすると、a[i] * b[i]は独立に実行できるはずなのに順番待ちをしていることになり、効率が落ちるだろう(下手をするとスレッド割り振りをしている分だけ逐次にさえ劣るかもしれない)ということが予想される。事前計算されてしまうかもしれないハードコードの代わりにa, bをcinから受け取ることにし、出力を取り除いてstd::chrono::system_clock::now()による簡易的な計測を行ったところ、この程度の計算ではスレッド割り振りのオーバーヘッドの方が計算自体より遥かに大きいことが分かった(Ubuntu on WSLという環境、前の計算結果がキャッシュされているかもしれない、10^n回反復させていないという状態なので実際の値は示さないが10^2倍単位で逐次がreductionより速く、reductionはcriticalより数倍速かった)。

午後

uGUIによるタブメニューを構築していた。インスペクタのOn Click()に指定するだけでUIのEventが発生することがこれほどありがたいとは思わなかった。あっという間にUI内で完結したメニュー操作処理ができた。World Space UIとして表示しているので、メニューの中を信号機のようなオブジェクトが突き抜けてしまうが、何か良い方法はないかと考えている。遠くではなく手元にメニューを持ってきてもいいかもしれない。

ABC140はA-Dが解けた。打ち間違いによるWAで10分失ってしまったのが惜しまれる。