Mae向きなブログ

Mae向きな日記のブログ版。ようやくこちらに移行してきました。

OpenMP

7/16の日記に、『まつもとゆきひろ コードの世界?スーパー・プログラマになる14の思考法』を読んだ感想として、「第10章 プログラムの高速化と並列化」が一番興味深かったと書きました。本章の最後に、「2つの法則」というコラムがあるのですが、その中で、以下のようなことが書かれています。

 今まではムーアの法則の恩恵で、CPUが勝手に早くなることでソフトウェアが高速化されてきました。ソフトウェア開発者はなにもしなくても新しいコンピュータに乗り換えるだけで自動的に高速化されていたのです。
 しかし、そのような幸せな時代は終わりに近づいています。ひとつのCPUの高速化は限界に近づいていて、むしろ、1チップに複数のCPUを詰め込むマルチコアとかメニーコアという方向に進歩しています。今後はたくさんあるCPUに上手にタスクを分割できるソフトウェアだけがムーアの法則の恩恵を受け、高速化されるようになります。

これを読んだとき、今から20年近く前の学生時代を思い出しました。
学生時代には、並列アルゴリズムゼミ(通称パラゼミ)という輪講があって、英語のテキストの章ごとに発表割り当てがありました。僕は、英語が苦手、かつ、シーケンシャルなアルゴリズムについても理解できていないのに、ましてパラレルアルゴリズムなんてとんでもないと思いながら出席していました。
しかし、以前も書いたことがあると思うんですが、大学のカリキュラムってよく考えられていたんだなと思います。当時は、C言語もあまりできないのに,なんでLispなんかする必要があるんだろうとか、SunOSって何?って思ったりもしたんですが、今となっては非常に有用な講義だったんだなと思います。もっと真剣に前向きに取り組んでおけば良かったなと思います。自分が教員になって「素直な子は伸びる」と言われていた意味を実感しました。僕の学生時代のように半信半疑で講義を受ける学生よりも、素直な気持ちで取り組む学生の方がやっぱり伸びるんでしょうね。
なんだか懐かしい気持ちになりましたが、ちょっと調べてみると、OpenMPなるものを知りました。
OpenMPは、共有メモリマルチプロセッサ上のマルチスレッドプログラミングのためのAPIだそうです。OpenMPは、新しい言語ではなく既存の逐次言語にプラグマ(#pragma)で指示を加えることにより並列化を行います。
以下を参考に取り組んでみました。
http://www.hpcs.is.tsukuba.ac.jp/~taisuke/EXPERIMENT/openmp-txt.pdf

まずは、helloものです。

hello_omp.c

#include <stdio.h>
#include <omp.h>

int main(void)
{
#pragma omp parallel
  {
    printf("hello world from %d of %d\n",
           omp_get_thread_num(), omp_get_num_threads());
  }
  return 0;
}

実行結果

参考URLでは、omccコマンドでコンパイルしていましたが、Ubuntu上のgccコンパイルできました。
プログラムでは、printf文は1行ですが、以下の実行結果のように2行に渡ってhello worldが出力されています。

$ gcc --version
gcc (Ubuntu 4.3.3-5ubuntu4) 4.3.3
Copyright (C) 2008 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ gcc -fopenmp hello_omp.c
$ ./a.out
hello world from 0 of 2
hello world from 1 of 2

sum_omp.c

次は、配列の要素の和を求めるものです。forループを並列化していますが、sum関数内のreductionというのが面白いですね。並列に計算したそれぞれのsを最後に足し合わせてるんですね。

#include <stdio.h>
#include <omp.h>

#define SIZE 1000

int main(void)
{
  int i;
  int A[SIZE];
#pragma omp for  
  for (i = 0; i < SIZE; i++) {
    A[i] = i;
  }
  printf("sum = %d\n", sum(A, SIZE));
}

int sum(int *a, int n)
{
  int i, s = 0;
#pragma omp parallel
  {
#pragma omp for reduction(+:s)
    for (i = 0; i < n; i++) {
      s += a[i];
    }
  }
  return s;
}

実行結果

$ gcc -fopenmp sum_omp.c
$ ./a.out
sum = 499500