インテル Parallel Composerの新機能――並列プログラムを容易に実装できる「インテル Cilk Plus」入門 6ページ

配列の全要素を順番に処理するリダクション関数

 Cilk Plusでは「リダクション関数」という、配列を引数に取って各要素の和や積、最小値/最大値などを求める関数が用意されている。たとえば「__sec_reduce_add()」は、引数として与えた配列の全要素の和を返す関数だ。

int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int sum;

sum = __sec_reduce_add(a[:]);  ← aの全要素を加算する
printf("%d\n", sum);  ←「55」が表示される

 Cilk Plusでは加算だけでなく、乗算や最大値/最小値の演算、ゼロ/非ゼロを判断するなど、さまざまなリダクション関数が用意されている(表4)。

表4 Cilk Plusで提供されているリダクション関数
関数プロトタイプ 説明
__sec_reduce_add(a[:]) すべての要素の和を返す
__sec_reduce_mul(a[:]) すべての要素の積を返す
__sec_reduce_all_zero(a[:]) すべての要素がゼロの場合真を、そうでない場合偽を返す
__sec_reduce_all_nonzero(a[:]) すべての要素が非ゼロの場合真を、そうでない場合偽を返す
__sec_reduce_any_nonzero(a[:]) 要素のどれか1つ以上が非ゼロの場合真を、そうでない場合偽を返す
__sec_reduce_min(a[:]) 要素中の最小値を返す
__sec_reduce_max(a[:]) 要素中の最大値を返す
__sec_reduce_min_ind(a[:]) 要素中で最小の値を持つ要素のインデックスを返す
__sec_reduce_max_ind(a[:]) 要素中で最大の値を持つ要素のインデックスを返す
__sec_reduce(fun, identity, a[:]) 各要素を引数として順番に関数funを実行し、その結果を返す

 用意されているリダクション操作だけでなく、「__sec_recuce()」関数を利用することで任意のリダクション処理を記述することも可能だ。__sec_reduce()関数では指定した配列の各要素を引数として、繰り返し指定した関数を呼び出し、最後に呼び出された関数の戻り値を__sec_reduce()の戻り値として返す、という動作を行う(図5)。

図5 __sec_reduce()の動作
図5 __sec_reduce()の動作

 たとえばint型の配列に対し、それぞれの要素の2乗の和を返すようなリダクション操作を行う場合、与える関数funは次のようになる。

/* 第1引数(new_value)には次の要素の値が、
第2引数(previous_total)には直前に実行されたfun()の戻り値が与えられる /*
int fun(int new_value, int previous_total) {
  // 与えられた要素の値を2乗し、直前のfun()の戻り値に加算して返す
  return new_value*new_value + previous_total;
}

 これらリダクション関数はあらかじめ並列化が行われているため、たとえばforループなどを使って独自に同様の処理を記述するよりも高速に実行される。配列に対し連続してアクセスするような場合、これらの関数が利用できないか検討してみると良いだろう。

配列の全要素に同じ処理を実行するScalar Function Maps

 配列のすべての要素に対し、同一の処理を実行したいという場合に有用なのが「Scalar Function Maps」と呼ばれる機能だ。たとえば、配列aの各要素を引数にsin()を実行し、その結果を配列bに格納したいという場合、次のように記述できる。

a[:] = sin(b[:]);

 あらかじめsin()やcos()、pow()といった数学関数が定義されているほか、新たに関数を定義して使用することもできる。

a[:] = pow(b[:], c);  ← a[:] = b[:]**cに相当
a[:] = pow(c, b[:]);  ← a[:] = c*:に相当

// ユーザー定義関数
__declspec(vector) float foo(float a, float x, float y) {
    return (a * x + y);
}
a[:] = foo(10.0, x[:], y[:]);

 ユーザー定義の関数を使用する場合、呼び出される関数(Elemental Functionと呼ばれる)は「__declspec(vector)キーワードを付加して宣言する。また、Elemental Functionでは下記が使えないという制約があるので注意してほしい。

  • forやwhile、do、gotoといったループ
  • switch文
  • クラスや構造体に対する処理
  • 非elemental functionsの呼び出し
  • _Cilk_spawn
  • [:]を使った配列操作

 Scalar Function Mapsでは自動的に並列化が行われるほか、数学関数についてはSIMDライブラリが呼ばれるため、より高速に実行できるのが特徴だ。

手軽で使いやすく、高パフォーマンスが期待できるCilk Plus

 Cilk Plusは簡潔で直感的に分かりやすい仕様を備えており、OpenMPなどほかの並列プログラミング技術と比べると覚えるべきキーワードなども少なく、比較的習得が容易であると思われる。また、CとC++の両方で利用できるのもメリットだ。

 また、Parallel Studio 2011では全面的にCilk Plusサポートが取り込まれている。たとえばパフォーマンス解析ツールであるParallel InspectorにはCilk Plusを使用したプログラムの解析に対応しているほか、並列化支援ツールであるParallel AdvisorではCilk Plus向けのコーディングガイドが用意されている。Parallel Studioを利用しており、これから新たにプログラムを並列化する、という場合、Cilk Plusは並列プログラムを実装するツールとして第1の候補に挙げられるだろう。