OpenMP* でベクトル化された並列ループを簡単に作成する

同カテゴリーの次の記事

ループをベクトル化するための条件

この記事は、インテル® デベロッパー・ゾーンに掲載されている「How to Create Vectorized, Multicore Loops in OpenMP with Ease」の日本語参考訳です。


OpenMP* のプラグマ宣言子を使用すると、1 つのループでベクトル化とマルチコア並列化の両方を適用することができます。この記事では、その方法を説明します。

Go Parallel を愛読されている方は、Go Parallel が並列プログラミングの 2 つの主要な分野 (マルチコア・プログラミングと SIMD ベクトル化) に注目していることをご存知でしょう。OpenMP* でループを並列化する場合、どちらも指定することができます。

すでにご存じのように、OpenMP* オプションによる並列化の制御はプラグマにより指定します。プラグマは、omp から始まり、指示句が続きます。指示句は、大文字と小文字が区別されます。

SIMD ベクトル化

ループをベクトル化するには、simd 構文を使用します。

#pragam omp simd

(注: simd 構文を使用するには、C++ コンパイラーで OpenMP* 4.0 がサポートされていなければなりません。インテル® C++ コンパイラー 14.0 以降はサポートしています。)

ベクトル化されたループは、それを作成した現在のスレッドで実行されます。各コアには個別の SIMD レジスターがあり、それぞれ別のベクトル化されたループをサポートできるため、これは重要です。(筆者がベクトル化について学んだとき、この点が疑問でしたが、前述のようにインテルのエンジニアが解決してくれました。)

以下に、simd プラグマの使用例を示します。

#include <iostream>
#include <omp.h>
using namespace std;
int main() {
    const int SIZE = 300;
    int x[SIZE];
    int y[SIZE];
    int z[SIZE];
    // ループの初期化
    for (int i=0; i<SIZE; i++) {
        x[i] = i;
        y[i] = i * 2;
        z[i] = 0;
    }
    // メインループ
#pragma omp simd
    for (int i=0; i<SIZE; i++) {
        z[i] = x[i] + y[i];
    }
    return 0;
}

このコードをインテル® コンパイラーでコンパイルするには、/Qopenmp オプションに加えて、/Qopenmp-simd オプションを指定する必要があります。(注: ドキュメントには、/Qopenmp オプションを指定するとデフォルトで SIMD が有効になると記述されていますが、筆者がテストした際は SIMD が有効にならなかったため、/Qopenmp-simd を追加しました。)

マルチコア並列化

複数のコアに for ループの実行を分配するには、次のプラグマを使用します。

#pragma omd parallel for

前述のコード例で “simd” を “parallel for” に変換するだけです。簡単でしょう。

マルチコア並列化とベクトル化の併用

2 つのプラグマを併用することで、マルチコアによる並列化とベクトル化の両方を行うことができます。ここでは、意図的に最初にマルチコアベクトル化、次にベクトル化の順で表記しています。ループを並列化する場合、最初に複数のコアに分配します。次に、それぞれのコアで並列に実行されるループの反復をベクトル化することができます。

インテル® コンパイラーでマルチコア並列化とベクトル化を併用するには、次のプラグマを使用します。

#pragma omp parallel for simd

そして、/Qopenmp と /Qopenmp-simd を指定してコンパイルします。

ベクトル化された関数

SIMD ベクトル化を使用すると、ループ内で数値演算を行う代わりに、それらを異なる関数内に配置することができます。そのためには、declare simd 構文を使用して関数をベクトル化された関数としてコンパイラーに通知します。

#pragma omp declare simd

この構文を追加し忘れても、コードは問題なくコンパイルし実行できます。ただし、その場合ベクトル化は行われません。これは、生成されるアセンブリー・コードで確認できます。/S オプションを指定してコンパイルすると、アセンブリー・コードを含む .asm ファイルが生成されます。declare simd 構文を使用しない場合、関数のアセンブリー・コードは次のようになります。

        add       ecx, edx
        mov       eax, ecx
        ret

つまり、2 つの整数レジスターを加算しているだけです。declare simd 構文を追加すると、次のように関数はベクトル化された加算を行います。

        paddd     xmm0, xmm1
        ret

まとめ

ここでは、OpenMP* による初歩的な並列化について説明しました。今後は、徐々にレベルを上げていくつもりです。例えば、OpenMP* により複数のループを 1 つに結合する方法を紹介します。simd 構文と parallel 構文にはいくつかのオプションがあり、コードのコンパイル方法を細かく制御できます。記事で紹介する例には、インテル® コンパイラーを使用しますが、GNU* コンパイラーと Microsoft* コンパイラーも OpenMP* をサポートしています。今後の記事にご期待ください。

関連記事

  • OpenMP 4.0 を使用してプログラムで SIMD を有効にするOpenMP 4.0 を使用してプログラムで SIMD を有効にする この記事は、インテル® デベロッパー・ゾーンに掲載されている「Enabling SIMD in program using OpenMP 4.0」の日本語参考訳です。 OpenMP 4.0 仕様で導入された主要機能の 1 つは、プログラム中でプラグマを使って明示的にベクトル化/SIMD […]
  • ループのベクトル化によるプログラムの最適化ループのベクトル化によるプログラムの最適化 この記事は、インテル® デベロッパー・ゾーンに掲載されている「Program Optimization through Loop Vectorization」の日本語参考訳です。 はじめに この記事では、有限差分ステンシル計算を例に、インテル® メニー・インテグレーテッド・コア (インテル® MIC) […]
  • OpenMP* 関連のヒントOpenMP* 関連のヒント この記事は、インテル® ソフトウェア・サイトに掲載されている「OpenMP Related Tips」の日本語参考訳です。 OpenMP* ループの一重化 各スレッドで処理される作業の粒度を減らすことで利用可能なスレッド全体で分割される反復の総数を増やすには、OpenMP* collapse […]
  • ベクトル異形関数でのプロセッサー ID 指定ベクトル異形関数でのプロセッサー ID 指定 インテル® C/C++ および Fortran コンパイラーのバージョン 16.0 以降では、プログラマーがスカラー関数に対応するベクトル関数を明示的に記述することを可能にする、ベクトル異形関数 (Vector Variant Function) をサポートしています。 Windows* および Linux* […]
  • インテル® Advisor コマンドラインと MPIインテル® Advisor コマンドラインと MPI この記事は、インテル® デベロッパー・ゾーンに公開されている「Command Line and MPI」の日本語参考訳です。 インテル® Advisor 2016 には、Fortran、C および C++ (ネイティブ/マネージド) […]