インテル® Xeon Phi™ プロセッサーにおけるインテル® AVX-512 プログラミング要約

同カテゴリーの次の記事

インテル® Xeon Phi™ プロセッサー向けプログラミング要約

この記事は、inside HPC に公開されている「Intel Xeon Phi Processor Intel AVX-512 Programming in a Nutshell」の日本語参考訳です。


スペシャルゲスト、James Reinders(ジェイムス・レインダース)氏による HPC 向けのインテル® Xeon Phi™ プロセッサー向けプログラミング・シリーズ


James Reinders、並列プログラミング・エンスージアスト

以前の記事「インテル® Xeon Phi™ プロセッサー向けプログラミング要約」では、インテル社の 72 コア・プロセッサー Knights Landing (開発コード名) の概要と、「インテル® Xeon Phi™ プロセッサーのメモリーモード・プログラミング (MCDRAM) 要約」 、そして、「インテル® Xeon Phi™ プロセッサーのクラスターモード・プログラミング (メモリーモードとの相互作用) 要約」でメモリーとクラスターモードの概要を説明しました。

この記事では、インテル® アドバンスト・ベクトル・エクステンション 512 (インテル® AVX-512) 命令の使い方を説明し、パフォーマンスに影響するいくつかのベクトル化のテクニックを紹介します。

インテル® AVX-512 – 4 つのトピック

私の著書、『インテル® Xeon Phi™ プロセッサー・ハイパフォーマンス・プログラミング – Knights Landing エディション』 (英語) (日本語翻訳版はこちら) では、インテル® AVX-512のベクトル化に関するトピックを 4 つに分けて説明しています。この記事では、それらを重要と思われる順番で紹介していきます。

4 つのトピック:

  • ベクトル化
  • ベクトル化を支援するツールによる解析とアドバイス
  • ベクトル化のためのテンプレート・ライブラリー
  • インテル® AVX-512 組込み関数

インテル社から多くのドキュメントが公開されているため、書籍では命令セットの詳細については触れていません。この記事では、インテル® AVX-512 命令の紹介から始めましょう。

インテル® AVX-512 概要

インテル® AVX-512 は、x86 ベクトル命令セットの長い歴史における最新の命令セットです。ベクトル命令は、一般的に SIMD 命令として知られており、一度に複数の操作を行います。512 ビットの浮動小数点命令では、命令ごとに 8 つの倍精度 (それぞれ 64 ビット) または 16 の単精度 (それぞれ 32 ビット) 操作が可能です。

インテル® Xeon Phi™ プロセッサーは、これまでのすべての SIMD 命令 (インテル® MMX®、インテル® ストリーミング SIMD 拡張命令 (インテル® SSE)、インテル® SSE2、インテル® SSE3、インテル® SSSE3、インテル® SSE4.1、インテル® SSE4.2、インテル® AVX、およびインテル® AVX2) をサポートします。命令レベルの並列性が高いため、最高のパフォーマンスは常にインテル® AVX-512 を使用することで得られます。インテル® AVX-512 は、インテル® AVX/インテル® AVX2 (256 ビット幅) のおよそ 2 倍、各種インテル® SSE (128 ビット幅) 命令セットの 4 倍の最大パフォーマンスをもたらします。

インテル® AVX-512 は、インテル® AVX とインテル® AVX2 のようにコンパイラーがターゲットを容易に生成できるよう、よりバランスよく設計されています。限定されるインテル® SSE やインテル® MMX® 命令セットよりもコンパイラーがターゲットの命令を生成しやすいインテル® AVX の新機能をいくつか組み合わせています。

インテル® AVX-512 は、ZMM0 – ZMM31 と呼ばれる 512 ビット幅のレジスターを 32 個備えています。32 個の ZMM レジスターが 2K のレジスター空間を表すのは注目に値します。インテル® AVX がインテル® SSE とインテル® MMX® のサポートを提供するように、インテル® AVX レジスター (YMM) は、ZMM レジスターの下位 256 ビットにマップされます。

コンパイラーが生成したコードと並列処理に役立つインテル® AVX-512 の 2 つの機能: (a) インテル® AVX-512 命令の “埋め込み” 制御は、丸め制御、ブロードキャスト、浮動小数点フォルトの抑制、メモリーフォルトの抑制など、これまでのグローバルな概念を命令ごとに制御を行います。(b) プレディケーション向けの 8 つのマスクレジスター。

インテル® AVX-512 は、インテル® AVX-512 基本命令やインテル® AVX-512 競合検出命令 (CD) などにグループ化され、それぞれはインテル® AVX-512 実装の一部であり、まだ拡張の余地が残されています。インテル® Xeon Phi™ プロセッサーは、インテル® AVX-512 指数および逆数命令 (ER) とインテル® AVX-512 プリフェッチ命令 (PF) と呼ばれる 2 つの固有の命令グループを導入しています。インテルはこの最初のインテル® AVX-512 に続き、将来のインテル® Xeon® プロセッサーでインテル® AVX-512 ダブルワードおよびクワッドワード命令、インテル® AVX-512 バイトおよびワード命令、さらにインテル® AVX-512 ベクトル長拡張と呼ばれる整数とビット操作機能を拡張するサブグループを追加する予定であると公表しています。これらは、将来のインテル® Xeon Phi™ プロセッサーにも導入される可能性がありますが、現在の世代ではサポートされていません。

ベクトル化

使用されるインテル® AVX-512 命令の大部分は、通常プログラマーが記述するプラグマやディレクティブによるヒントを基にコンパイラーによって生成されます。C、C++、および Fortran など並列指向が乏しい構造の言語では、ヒントの導入は重要です。これらの言語は、マルチコアやメニーコア・プロセッサー登場以前から存在するため、ベクトル化や並列化機能を効率良く使用するにはプログラマーによる指示を必要とします。また、組込み関数によってもコンパイラーはインテル® AVX-512 命令を生成することができます。

これらすべての面で、インテル® AVX-512 は従来のベクトル命令セットと同じです。インテル® AVX-512 における課題とテクニックは従来と同じですが、より高いパフォーマンスに直結する最適化を適用するため、コンパイラーにより多くの柔軟性を与えます。

次のプログラムは、プログラマーによるヒントなしではベクトル化されないでしょう。ここでは OpenMP* ディレクティブを使用します。

__declspec(align(16)) float a[MAX], b[MAX], c[MAX];
#pragma omp simd
for (i=0;i<max;i++)
c[i]=a[i]+b[i];

ディレクティブなしでベクトル化されない理由は、C/C++ コンパイラーが引数のエイリアシングによるオーバーラップを仮定する必要があるためです。Fortran にはこの問題はありません。そのため、Fortran ではこのようなディレクティブの使用は適切ではないかもしれません。

コードのベクトル化を妨げる多くの原因がありますが、引数のエイリアシングはそのうちの 1 つです。この簡単な例では、コンパイラーに引数のエイリアシングの可能性やベクトル化を妨げるそのほかの原因を無視させるため、OpenMP* の “simd” ディレクティブを使用しました。この問題を解決するほかの解決策もあります。例えば、C99 のキーワード “restrict” を引数リストに記述できます。ベクトル化できない原因がエイリアシングのみであれば、 “restrict” 修飾子はコードのベクトル化を可能にします。より複雑な状況では、”simd” ディレクティブがいくつかのベクトル化を妨げる要因を排除します。

以前のインテル® Xeon Phi™ コプロセッサーの書籍では、ベクトル化に関するトピックは単独の章で説明されていました。新しい書籍、『インテル® Xeon Phi™ プロセッサー・ハイパフォーマンス・プログラミング』では読者の助言に従い、コンパイラーを単独で使用する際の最も一般的なオプションや最適化を支援するツールなど 3 つのトピックを追加しました。

ベクトル化を支援するツールによる解析とアドバイス

ベクトル化に関する最もよくある質問は、”なぜベクトル化されないのか?” と “さらにベクトル化できる可能性はあるか?” というものです。以前は、このような問いに対する答えは、コンパイラーのダンプを調査してパフォーマンス解析ツールを実行し、理論上のピーク・パフォーマンスと比較することでした。

インテルのエンジニアは、”ベクトル化アシスタント” と呼ばれるツールを開発しこれを簡単に行えるようにしました。この機能は、インテル® Advisor と呼ばれる優れたスレッドとタスク解析を行うツールの一部となりました。ツールの概要についてはオンライン (英語) ご覧いただけます。また、評価版のツールはインテル社の Web サイト (英語) からダウンロードできます (学生や研究者は長期の評価版を利用できます。詳細はウェブで確認してください)。

これは大変役立つツールであり、私のお気に入りです。書籍では、このツールに関する章を設けることにしました。概念はシンプルです: プログラムがどれくらい計算を行っており、すべての計算にインテル® AVX-512 命令を適用し完全に活用できた場合に、どれくらい理論上のピーク・パフォーマンスに近づくことができるかです。この概念は「ルーフライン解析」と呼ばれます。ルーフラインに関する優れた論文がカリフォルニア大学バークレー校 (英語) とリスボン工科大学 (英語) の研究者により公開されています。

ベクトル化能力の限界、データレイアウトによる計算へのデータの供給の限界、およびそのほか多くの問題は、”ルーフライン” を下回っていることが確実な場合に対処できます。一方、計算が完全にパフォーマンスを活用している場合、それ以上のベクトル化の方法がないことを意味します。しかし、より少ない計算による別のアプローチが可能であるかもしれないことを忘れてはいけません。

ベクトル化のためのテンプレート・ライブラリー

私たちは以前、ベクトル化のためのテンプレート・ライブラリーの開発を試みたことがありますが、SIMD Data Layout Templates (SDLT) (英語) ほど関心を集めることはできませんでした。SDLT は、インテル® C++ コンパイラー (評価版と製品版) に同梱されています。

テンプレート・ライブラリーは、コードが任意のプログラミング・スタイルを維持しながら、構造体配列 (AoS) タイプのインターフェイスを使用することを可能にし、テンプレート・ライブラリーによって変換される適切なアクセスパターンから利点を得ることができます。SDLT は、最小限の修正で不適切なデータレイアウトから適切なデータレイアウトへコードを変換します。インテル社の Web サイト (英語) に変換の例が示されています。

インテル® AVX-512 組込み関数

私たちは当初、書籍に組込み関数に関する説明を掲載することを計画していませんでした。15 年前、私はアセンブリー言語に変わるプログラミングのオプションとして、組込み関数を積極的に推奨していました。組込み関数は、アセンブリー言語のように強力であるにも関わらず、コンパイラーが自動的にプログラム上のインターフェイスを処理し、C/C++ コードとのレジスターの割り当てをスムースに統合します。これらの利点は、ほとんどのコンパイラーによる組込み関数サポートによって提供され、組込み関数は広く利用されるようになりました。

私は、コンパイラーへのヒントは通常のコードでは有効であり、高いポータビリティーがあることを理解しています。組込み関数は特定の命令セットの利用に制限されます。例えば、インテル® AVX 組込み関数を使用してコードを記述した場合、インテル® AVX-512 の利点を活用するにはコードを書き直す必要があります。C/C++ で記述している場合、そのようなことはありません。コンパイラーは対象とするベクトル命令セットにかかわらずコンパイルを行ってくれます。

私が組込み関数を再び推奨するようになったのには、2 つの理由があります。それは、(a) 多くの重要な使い方を目にしてきたことと、(b) 組込み関数に対して、プログラマーにどのような混乱があるかを知ったためです。組込み関数は容易に理解でき簡単に使用できることが、広く理解されていませんでした。組込み関数の使用は限定的だと考えてはいますが、私は利用できる場所とその利点を理解できると思っています。

組込み関数の利点は、プログラマーが低レベルのアセンブリーを記述する必要がなく、さらに低レベルの命令スケジュールやレジスター割り当てなどマイクロアーキテクチャーに依存するすべてをコンパイラーに任せることができることです。また、組込み関数とコンパイラーは、特定の組み合わせをサポートするプラットフォーム上で 2 つの SIMD 命令を融合して、効率良いコードを生成することができます。

組込み関数を簡単に使用できることを示すため、ここでは 16 個の浮動小数点値を含む 2 つの配列をロードして、それらを加算する例を使います。

__m512 simd1 = _mm512_load_ps(a); // 16 個の単精度をメモリーから読み込み
__m512 simd2 = _mm512_load_ps(b); // 16 個の単精度をメモリーから読み込み
__m512 simd3 = _mm512_add_ps(simd1, simd2); // 上記を加算

組込み関数は、関数呼び出しと同じように見えます。組込み関数を使用するには、はじめにインテル® AVX 組込み関数用のヘッダーファイル (immintrin.h) をインクルードして、目的とする組込み関数を呼び出します。通常の関数呼び出しと同様に、組込み関数の引数と戻り型の規則に従う必要があります。インテル社の Web サイト (英語) では、多くのドキュメントが公開されています。

最初に組込み関数を使用する際に戸惑うことは、命令のサフィックスが何を意味するか理解することです。サフィックスは、命令全体を見るときに理解すべきものです。以下は、一般的なインテル® AVX-512 命令向けのデコーダーです。

インテル® AVX-512 を理解するためのプログラム

この例は、インテル® AVX-512 の使い方がどれくらい容易であるかを示す例です。

#include <stdio.h>
#include "immintrin.h"

void print(char *name, float *a, int num) {
    int i;
    printf("%s =%6.1f",name,a[0]);
    for (i = 1; i < num; i++)
        printf(",%s%4.1f",(i&3)?"":" ",a[i]);
    printf("\n");
}

int main(int argc, char *argv[]) {
    float a[] = { 9.9,-1.2, 3.3,4.1, -1.1,0.2,-1.3,4.4,2.4, 3.1,-1.3,6.0, 1.5,2.4, 3.1,4.2 };
    float b[] = { 0.3, 7.5, 3.2,2.4, 7.2,7.2, 0.6,3.4,4.1, 3.4, 6.5,0.7, 4.0,3.1, 2.4,1.3 };
    float c[] = { 0.1, 0.2, 0.3,0.4, 1.0,1.0, 1.0,1.0,2.0, 2.0, 2.0,2.0, 3.0,3.0, 3.0,3.0 };
    float o[] = { 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 };

    __m512 simd1, simd2, simd3, simd4;
    __mmask16 m16z = 0;
    __mmask16 m16s = 0xAAAA;
    __mmask16 m16a = 0xFFFF;

    print(" a[]",a,16);
    print(" b[]",b,16);
    print(" c[]",c,16);

    if (_may_i_use_cpu_feature(_FEATURE_AVX512F)) {
        simd1 = _mm512_load_ps(a);
        simd2 = _mm512_load_ps(b);
        simd3 = _mm512_load_ps(c);
        simd4 = _mm512_add_ps(simd1, simd2);
        _mm512_store_ps(o, simd4);
        print(" a+b",o,16);

        simd4 = _mm512_sub_ps(simd1, simd2);
        _mm512_store_ps(o, simd4);
        print(" a-b",o,16);

        simd4 = _mm512_mul_ps(simd1, simd2);
        _mm512_store_ps(o, simd4);
        print(" a*b",o,16);

        simd4 = _mm512_div_ps(simd1, simd2);
        print(" a/b",(float *)&simd4,16);

        printf("FMAs with mask 0, then mask 0xAAAA, ");
        printf("then mask 0xFFFF\n");

        simd4 = _mm512_maskz_fmadd_ps(m16z,simd1,simd2,simd2);
        print("a*b+b",(float *)&simd4,16);

        simd4 = _mm512_maskz_fmadd_ps(m16s,simd1,simd2,simd3);
        print("a*b+b",(float *)&simd4,16);

        simd4 = _mm512_maskz_fmadd_ps(m16a,simd1,simd2, simd3);
        print("a*b+b",(float *)&simd4,16);

    }

    return 0;

}

このプログラムをインテル® AVX-512 がサポートされるマシン上で実行すると、次の出力が得られます。

インテル® AVX-512 がサポートされないマシン上では、最初の 3 行のみが出力されます。インテル® AVX-512 がサポートされないマシン上でプログラム全体を実行したい場合、インテル® Software Development Emulator (インテル® SDE) (英語) を使用してコンパイル済みのプログラムを実行することができます。インテル® SDE を使用して、同じマシン上でコンパイルしたプログラムを実行し、インテル® AVX-512 なしでインテル® Xeon Phi™ プロセッサー (インテル® AVX-512 対応) で表示されるメッセージを表示することができました。

組込み関数は万人向けではありません。そして、コンパイラーが命令セットを活用するコードを自動生成できない場合にのみ利用すべきであると考えます。C/C++/Fortran で記述された解決策は、効率良いコードにコンパイルでき、新たなチューニングを必要とせずに将来のハードウェア上でも実行できます。コンパイラーは、C/C++/Fortran の制限を考慮しながらベクトル化を行っています。しかし、組込み関数は画期的なプログラミングを可能にする代替え方法を提供します。

組込み関数を利用したことがないプログラマーの方は、この記事から何かヒントを掴んでくれることを期待しています。ここで紹介する例から始めてみてください。インテル社のオンラインガイド (英語) は、組込み関数を良く利用するプログラマーの皆さんにも推奨されます。

まとめ

インテル® Xeon Phi™ プロセッサーは注目すべき x86 デバイスです。世界で最も強力なスーパーコンピューターの構成要素として、最大 72 個のコアを搭載し、パフォーマンスを向上する多くの設計上の特徴を備えています。インテル® Xeon Phi™ プロセッサーにおいて最も高いパフォーマンス・レベルに到達するには、インテル® AVX-512 命令セットの利用が不可欠です。

本稿は、Knights Landing (開発コード名) に関するシリーズの 4 番目の記事となります。最初の「インテル® Xeon Phi™ プロセッサー向けプログラミング要約」で概要を説明し、それに続く 「インテル® Xeon Phi™ プロセッサーのメモリーモード・プログラミング (MCDRAM) 要約」と「インテル® Xeon Phi™ プロセッサーのクラスターモード・プログラミング (メモリーモードとの相互作用) 要約」では、第 2 世代インテル® Xeon Phi™ プロセッサー (開発コード名: Knights Landing) の導入方法を説明してきました。今後の記事では、インテル® Xeon Phi™ プロセッサーの使用法について考えていきます。

関連記事