HPC/並列プログラミングポータルでは、HPC(High Performance Computing)プログラミングや並列プログラミングに関する情報を集積・発信しています。

新着トピックス

配列に対する処理をより簡潔に表す配列表記

 Cilk Plusではcilk_spawnやcilk_sync、cilk_forといった並列処理を指定するキーワードだけでなく、配列操作に関する表記についても拡張されている。これは「C/C++ Extensions for Array Notation(CEAN)」などと呼ばれており、「[]」を使ったC/C++の配列表記に新たな表現を追加するものだ。これを利用することで、配列の初期化や代入、配列要素同士の計算などを簡潔に記述できる。

 Cilk Plusでは、次のようにして配列の複数要素に対し一括処理を行える。

<配列変数>[<開始要素>:<長さ>]
<配列変数>[<開始要素>:<長さ>:<間隔>]

 たとえば、配列aに対し3番目から6番目までの4つの要素に10という値を代入する場合、次のように表記できる。

a[3:4] = 10;  ← a[3]、a[4]、a[5]、a[6]に10を代入

 上記の例では連続した要素にアクセスしているが、間隔を指定することで非連続した要素に対しても一括アクセスできる。

b[3:4:2] = 10;  ← b[3]、b[5]、b[7]、b[9]に10を代入

 開始要素と長さの両方を省略した場合、配列のすべての要素へのアクセスとなる。

c[:] = 10;  ← cのすべての要素に10を代入

 それぞれの要素に同じ値を代入するだけでなく、別の配列の値を代入することも可能だ。ただし、要素の数が異なる場合はエラーとなる。

a[3:2] = b[2:2];  ← a[3]にb[2]を、a[4]にb[3]を代入
c[:] = d[:];  ← 配列cに配列dの内容をコピー
e[3:3] = f[3:2]  ← 要素の数が異なるのでエラーとなる

 代入だけでなく各種演算も行える。また、変数で対象となるインデックスを指定することも可能だ。

a[3:2] = b[2:2] * 10;  ← a[3]にb[2]*10を、a[4]にb[3]*10を代入
a[0:10] = b[0:10] *c[5:10];  ← a[0]にb[0]*c[5]、a[1]にb[1]*c[6]、...を代入
a[:] = b[:] + 10;  ← 配列bの各要素に10を加えたものを配列aに代入
a[i:2] = b[i:2];  ← a[i]にb[i]を、a[i+1]にb[i+1]を代入

 これらの表記は、多次元配列に対しても適用できる。

a[:][:] = b[:][:];
c[2:4][2:3] = d[4:4][2][0:3] * 10;

 操作対象とする要素を配列で指定することも可能だ。このような操作は、「Gather」や「Scatter」などと呼ばれる。

unsigned index[] = { 1, 0, 3, 2 };
a[0:4] = b[index[0:4]];  ← a[0] = b[1]、 a[1] = b[0]、a[2] = b[3]、a[3] = b[2]という代入操作に相当
c[index[0:4]] = d[0:4];  ← c[1] = d[0]、 c[0] = d[1]、c[3] = d[2]、c[2] = d[3]という代入操作に相当

 また、条件分岐を含む文についても記述できる。たとえば、次のリスト8のようなコードは、リスト9のように記述することができる。

リスト8 if文を含む配列操作
for (int i = 0; i  n; i++) {
    if (a[i]  b[i]) {
        c[i] = a[i] - b[i];
    } else{
        d[i] = b[i] - a[i];
    }
}
リスト9 リスト7を「[:]」を使って記述する例
if (a[:]  b[:]) {
   c[:] = a[:] - b[:];
} else {
   d[:] = b[:] - a[:];
}

// 下記のようにも記述できる
c[0:n] = (a[0:n]  b[0:n]) ? a[0:n] - b[0:n] : c[0:n];
d[0:n] = (a[0:n] = b[0:n]) ? b[0:n] - a[0:n] : d[0:n];

 なお、これらの表記を利用する場合、事前に配列のサイズが明示的に指定されている必要がある。関数内などで静的に宣言されている配列については問題ないが、malloc()などで動的に確保した配列に対しては、明示的な型変換が必要となる。そのような場合、C99規格で規定されている可変長配列を使用すると良い(リスト10)。C99を使用するには「/Qstd=c99」オプションを付けてコンパイルを行う必要がある。

リスト10 動的に確保した配列に対する操作(C99の可変長配列を使用)
int* foobar(int length) {
	typedef int (*array_type)[len];
	int* ptr = (int*) malloc(sizeof(int) * length);  ← 長さlengthの配列を割当
	array_type a = (array_type) ptr;  ← ポインタを配列に変換
	(*a)[:] = 10;  ← 各要素に10を代入
	return ptr;
}

 「:」を使った表記はCPUのSIMD命令を用いて高速に処理され、またSIMD命令で実行できないような処理に関しても可能な限り並列で実行されるため、簡潔にコードを記述できるだけでなくパフォーマンス面でもメリットがある。