ループが小さい、すなわちループでの作業量が少ないと、大幅にパフォーマンスを向上させることはできません。これは、ループでの作業量に比べて、並列ループを起動するときのオーバーヘッドが大きくなるためです。このような状況では、コンパイラはループの融合を使用して、いくつかのループを 1 つの並列ループに融合し、ループを大きくします。同じ回数の繰り返しを行うループが隣接していると、ループの融合は簡単にしかも安全に行われます。次の例について考えてみましょう。
/* L1: 小さな並列ループ */
for (i=0; i < 100; i++) {
a[i] = a[i] + b[i]; /* S1 */
}
/* L2: 別の小さな並列ループ */
for (i=0; i < 100; i++) {
b[i] = a[i] * d[i]; /* S2 */
}
|
この例では、2 個の小さなループが隣どうしに記述されていて、次のように安全に融合することができます。
/* L3: 大きな並列ループ */
for (i=0; i < 100; i++) {
a[i] = a[i] + b[i]; /* S1 */
b[i] = a[i] * d[i]; /* S2 */
}
|
これによって、並列ループの実行によるオーバーヘッドを半分にすることができます。ループの融合は、別の場合にも役に立ちます。たとえば、同じデータが 2 個のループで参照されている場合には、この 2 個のループを融合すると、参照を局所的なものにすることができます。
ただし、ループの融合は常に安全に実行できるとはかぎりません。ループの融合によって、元々存在していなかったデータの依存関係が生成されると、実行結果が正しくなくなることがあります。次の例について考えてみましょう。
/* L1: 小さな並列ループ */
for (i=0; i < 100; i++) {
a[i] = a[i] + b[i]; /* S1 */
}
/* L2: a データの依存性がある小さなループ */
for (i=0; i < 100; i++) {
a[i+1] = a[i] * d[i]; /* S2 */
}
|
「3.7.2 ループの融合」でループの融合が実行されると、文 S2 から S1 に対するデータの依存性が生成されます。実際に、文 S1 の右辺にある a[i] の値が、文 S2 で計算されるものになります。これはループが融合されないと起こりません。コンパイラは、ループの融合を実行すべきかどうかを判断するために解析を行い、安全性と有効性を確認します。場合によっては、任意の数のループを融合できることがあります。このような方法でループの作業量を多くすると、並列実行が十分に有効であるようなループを生成することができます。