■サンプルファイルのダウンロードについて
本書掲載のサンプルファイルは、一部を除いてインターネット上のダウンロードサービスからダウンロードすることができます。詳しい手順については、本書の巻末にある袋とじの内容をご覧ください。
なお、ダウンロードサービスのご利用にはユーザー登録と袋とじ内に記されている番号が必要です。そのため、本書を中古書店から購入されたり、他者から貸与、譲渡された場合にはサービスをご利用いただけないことがあります。あらかじめご承知おきください。

・本書の内容についてのご意見、ご質問は、お名前、ご連絡先を明記のうえ、小社出版部宛文書（郵送または E-mail）でお送りください。
・電話によるお問い合わせはお受けできません。
・本書の解説範囲を越える内容のご質問や、本書の内容と無関係なご質問にはお答えできません。
・匿名のフリーメールアドレスからのお問い合わせには返信しかねます。
本書で取り上げられているシステム名／製品名は、一般に開発各社の登録商標／商品名です。本書では、™ および ® マークは明記していません。本書に掲載されている団体／商品に対して、その商標権を侵害する意図は一切ありません。本書で紹介している URL や各サイトの内容は変更される場合があります。

はじめに
近年においてソフトウェアエンジニアがハードウェアのアーキテクチャを意識する機会は激減しました。ソフトウェアエンジニアがハードウェアを意識していては、開発したソフトウェアは
ハードウェア依存となりポータビリティやスケーラビリティなどを失ってしまいます。
しかしながら、Wall Time にシビアなソフトウェアや計算量が大きなソフトウェアを開発する場
合、ハードウェアのアーキテクチャを意識したプログラミングを行うと、性能が格段に向上する場合があります。時代が代わり CPU 性能が大幅に向上しましたが、同時に多くの計算量を要求するソフトウェアも存在します。このため、アプリケーション開発エンジニアであっても、ハード
ウェアのアーキテクチャを意識せざるを得ない場面に遭遇します。
本書では、SIMD 命令、特に Intel 社 CPU の AVX-512 命令について解説します。Intel 社 CPU の
SIMD 命令は、MMX や SSE 命令、そして AVX や AVX2 と進化し、ついに 512 ビットをサポートした AVX-512 命令を装備するようになりました。AVX-512 は、整数演算を強化、ベクトル長の拡張、512 ビットだけでなく 128 ビットまたは 256 ビットの操作が可能です。また、マスクレジ
スターやいくつかの機能追加が行われています。
本書は、SIMD、特に AVX-512 命令にフォーカスして企画された書籍です。AVX 命令や SSE 命
令の解説本は散見されますが、AVX-512 命令の解説は多くありません。インストラクションの解説から、簡単なサンプルプログラム、そして 1 次元や 2 次元のサンプルプログラムまで示し、
AVX-512 命令の理解を深めます。
本書の対象読者は，以下のような人を対象としています。
● SIMD を理解したい人● AVX-512 命令を理解したい人● 高性能なプログラムを開発したい人

本書を参考に AVX-512 命令を理解し、高性能なプログラムの開発に役立ててください。微力ながら本書が学習の助けになれば幸いです。
謝辞
出版にあたり、お世話になった株式会社カットシステムの石塚勝敏氏に深く感謝いたします。
2021 年初春 都立東大和南公園にて 北山洋幸

iii

本書の使用にあたって
開発環境、および、実行環境の説明を行います。
■プラットフォーム
Windows 10 Home を開発・実行環境とします。一部のプログラムは、Ubuntu 20.04.2 LTS、そして Windows 10 Pro でも確認しています。
■オペレーティングシステム
基本的に 64 ビット Windows を使用しています。Ubuntu 20.04.2 LTS でも、ほとんどのプログ
ラムを確認しました。
■コンパイラ
Windows では、無償の Visual Studio Community 2019 を使用します。Ubuntu では、
g++ バージョン 9.3.0 を利用しました。
Intel 社の評価版を 30 日間は無償で利用できますので、登録し Intel Compiler (Intel oneAPI
Base ToolKit, v2021.2) でも試してみました。コンソールからの利用はもちろん、Windows 用を使用すると Visual Studio で Intel Compiler を利用できます。ただ、評価期間が比較的短いため、十分には使いこなせませんでした。
■ CPU
AVX-512 命令を使用していますので、比較的古い CPU が搭載されたパソコンではプログラムを実行できません。お使いの CPU が AVX-512 命令をサポートしているか確認してください。ただ、エミュレーターが提供されていますので、速度は低下しますが、古いパソコンであっても AVX512 命令の動作を理解するのは可能です。
■エミュレーター
自 身 の 環 境 で AVX-512 命 令 を 使 用 で き る CPU が な い 場 合、Intel Software Development
Emulator を利用すると、AVX-512 命令を使用できます。これについては、本文で詳しく解説し
ます。
■ URL
本文中に記載されている URL は執筆時点のものであり、変更される可能性もあります。リンク
先が存在しない場合、キーワードなどから自身で検索してください。

iv

■ユーザーアカウント「標準ユーザー」ではプログラムのインストールやアンインストールは制限されます。コンパイ
ラやインストールやセットアップで警告が出ることがありますので、なるべく「管理者」で実行
してください。
■バイト、ワード、ダブルワード、クワッドワード
バイトは 8 ビット（1 バイト）
、ワードは 16 ビット（2 バイト）
、ダブルワードは 32 ビット（4バイト）、クワッドワードは 64 ビット（8 バイト）です。高級言語では使用しませんが、アセンブリ言語を記述する場合、このような表現が使用されます。
■アドレスあるオブジェクトが格納されているメモリー位置のことです。
■ポインタ
あるオブジェクトが格納されているメモリー位置（アドレス）を保持するオブジェクトのことです。
■アセンブリ言語
プログラミング言語の 1 つで、コンピューターが直接解釈・実行できるマシン語と 1 対 1 に対応した言語です。いわゆる低水準言語（低級言語）の代表です。アセンブリ言語はマシン語の命
令と 1 対 1 に対応するニーモニック（英単語）などを使い記述します。現在では、抽象化された
高水準言語が主流ですが、CPU 性能を限界まで引き出すチューニングを行いたい場合や、組み込
みソフトウェアの開発などハードウェアに近い分野の開発などでは、今でも使われています。
■アセンブラアセンブリ言語で記述されたソースコードを、コンピューターが解釈できるマシン語へ翻訳するソフトウェアのことです。C++ 言語などに対するコンパイラと同じ位置づけです。
■カタカナ語の長音表記「メモリー」や「フォルダー」など、最近は語尾の「ー」を付けるのが一般的になっています。
なるべく、最近の表現に合わせましたが統一していません。
■ソースリストとソースコード
基本的に同じものを指しますが、ソースリストと表現する場合ソース全体を、ソースコードと表現する場合、ソースの一部を指す場合が多いです。

v

■命令やレジスター名

SIMD 命令やレジスター名の表現で、大文字と小文字の表現が混在します。例えば、ZMM1 とzmm1、そして vmovdqa64 と VMOVDQA64 は同じものです。

■ Linux と Ubuntu
Linux と Ubuntu を混在して使用していますが、本書のプログラムを動作させたのは、すべてUbuntu 上のみです。ただ、Ubuntu に限らず Unix（Linux）系と説明したほうが良さそうな場合は、Linux を用います。

vi

目 次

目

次

目次

はじめに...................................................................................................................................... iii

第1章

SIMD 概論 ............................................................... 1

1.1 フリンの分類..................................................................................................... 2
1.2 SIMD 概要......................................................................................................... 3
1.3 SIMD 命令で扱うデータ形式........................................................................... 4
1.4 SIMD 命令とは................................................................................................. 5

第2章

はじめてのプログラム ................................................ 7

2.1 C++ 言語で記述................................................................................................ 7
2.2 SSE 命令で記述..............................................................................................10
2.3 SSE 命令をインラインアセンブリで記述.....................................................11
2.4 AVX 命令で記述.............................................................................................13
2.5 AVX-512 命令で記述....................................................................................14
2.6 イントリンシックの解説.................................................................................17
2.7 ビルド方法.......................................................................................................19■ 2.7.1

第3章

Windows……19 ／■ 2.7.2

Ubuntu……21

AVX-512 ............................................................... 23

3.1 AVX-512 の詳細...........................................................................................23
3.2 AVX（AVX2）から AVX-512 へ...............................................................25
3.3 命令セットの判断............................................................................................25
3.4 イントリンシックとインクルードファイル...................................................30
3.5 AVX-512F の特徴.........................................................................................31
3.6 AVX-512 のレジスター................................................................................31
3.7 イントリンシックとレジスター......................................................................32vii

3.8 マスクレジスター............................................................................................33■ 3.8.1

移動……33 ／■ 3.8.2

コンプレス……37 ／■ 3.8.3

エクスパンド……39

3.9 プレディケーション........................................................................................41
3.10 ギャザー・スキャッター.................................................................................46■ 3.10.1

ギャザーの使用例……46 ／■ 3.10.2

スキャッターの使用例……51

3.11 丸め..................................................................................................................55■ 3.11.1■ 3.11.3■ 3.11.5

第4章

浮動小数点数の丸め……55 ／■ 3.11.2 構文……56
サンプル……57 ／■ 3.11.4 アセンブリコード……57
インラインアセンブラ……58

エミュレーター ....................................................... 61

4.1 Intel Software Development Emulator（Intel SDE）.......................62
4.2 ダウンロード...................................................................................................63■ 4.2.1

Linux の場合……64

4.3 インストール...................................................................................................65■ 4.3.1

Linux の場合……65

4.4 sde の起動（Windows）..............................................................................65
4.5 実行例（Windows）.......................................................................................67■ 4.5.1■ 4.5.2

エミュレーターを設定して実行……69
エミュレートプロセッサーを変更して AVX-512 命令のサポート状況を表示……70

4.6 sde の起動（Linux）......................................................................................72
4.7 実行例（Linux）..............................................................................................73■ 4.7.1

第5章

パスを設定して実行……74

アセンブリ言語で記述 .............................................. 77

5.1 Windows の例...............................................................................................77■ 5.1.1■ 5.1.3■ 5.1.5

ファイルの構成……77 ／■ 5.1.2 Visual Studio の例……82
コマンドラインからビルドする場合……86 ／■ 5.1.4 実行……86
呼び出し規約とレジスター……87

5.2 Ubuntu の例..................................................................................................90■ 5.2.1

第6章

ソースコード……91 ／■ 5.2.2

ビルド……95 ／■ 5.2.3

実行……95

AVX-512 とメモリー .............................................. 97

6.1 イントリンシックを使用した変数の宣言.......................................................97
6.2 アライメント...................................................................................................99

viii

目 次

■ 6.2.1■ 6.2.2__declspec で宣言（Visual Studio のみ）……99
イントリンシックで宣言……100

6.3 初期化........................................................................................................... 101■ 6.3.1■ 6.3.2■ 6.3.4__declspec で宣言初期化（Visual Studio のみ）……101__m128 で宣言初期化……102 ／■ 6.3.3 __m256 で宣言初期化……102__m512 で宣言初期化……103

6.4 メモリーの動的割付..................................................................................... 103■ 6.4.1■ 6.4.2_mm_malloc を使う方法……104_aligned_malloc を使う方法（Windows のみ）……105

6.5 ポータビリティ............................................................................................. 106

第7章

簡単な応用例 ........................................................ 107

7.1 配列の垂直演算............................................................................................. 107■ 7.1.1

2 つの配列を加算……107 ／■ 7.1.2

2 つの配列を乗算……112

7.2 配列の水平演算............................................................................................. 113■ 7.2.1■ 7.2.2

AVX 命令を使用し総和を求める……113
AVX-512 命令を使用し総和を求める……115

7.3 長いデータの総和を求める例...................................................................... 118

第8章

3D ベクトルの正規化（AOS、SOA）..................... 121

8.1 AOS と SOA............................................................................................... 121
8.2 AOS から SOA へのシャッフル................................................................. 123■ 8.2.1

512 ビットの AOS から SOA へのシャッフル……123■ 8.2.2……512 ビットの AOS から SOA へのシャッフル（アセンブリ言語版）……129■ 8.2.3

128 ビットと 256 ビットの AOS から SOA へのシャッフル……133

8.3 AVX で一般化した関数............................................................................... 140
8.4 AVX-512 で一般化した関数...................................................................... 144
8.5 AVX-512 で一般化した関数（アセンブリ言語版）.................................. 147

第9章

1 次元データ処理 .................................................. 153

9.1 長大な 1 次元配列に定数を乗ずる.............................................................. 153
9.2 プログラム全体............................................................................................. 156
9.3 1 次元配列同士の乗算.................................................................................. 160

ix

第 10 章フィルター.......................................................... 165

10.1 積和でフィルター......................................................................................... 165■ 10.1.1

共通プログラム……166

10.2 C++ による逐次処理プログラム................................................................. 168■ 10.2.1

実行方法……17210.3 ベクトル化したプログラム.......................................................................... 173
10.4 Cpfm クラス............................................................................................... 177
10.5 実行と性能評価............................................................................................. 180■ 10.5.1

スペクトル……183

10.6 AVX の例..................................................................................................... 184
10.7 データ操作ツール群..................................................................................... 185■ 10.7.1■ 10.7.3

第 11 章WAV ファイルのダンプ……186 ／■ 10.7.2 係数を求めるプログラム……188
テキストから WAV ファイルを生成……191

2 次元データ ....................................................... 193

11.1 読み込み関数................................................................................................ 193
11.2 ネガティブ処理............................................................................................. 197■ 11.2.1

プログラムのビルドと実行……200

11.3 スレッショルド処理..................................................................................... 201
11.4 合成処理........................................................................................................ 209■ 11.4.1■ 11.4.3

値の低いデータを選択……209 ／■ 11.4.2
加算……214値の高いデータを選択……212

11.5 AOS-SOA 応用........................................................................................... 216
11.6 単純な行列の積............................................................................................. 220■ 11.6.1

任意の行数と列数……224

11.7 データ操作ツール群..................................................................................... 224■ 11.7.1

画像ファイルをダンプ……225 ／■ 11.7.2

テキストを画像へ変換……227

第 12 章 AVX-512 系命令................................................. 231
12.1 加算の組込み関数......................................................................................... 231■ 12.1.1

解説……232

12.2 減算の組込み関数......................................................................................... 245■ 12.2.1

構文一覧……246 ／■ 12.2.2

解説……24812.3 乗算の組込み関数......................................................................................... 251■ 12.3.1

x

構文一覧……252 ／■ 12.3.2

解説……255

目 次

12.4 最大値と最小値を特定する組込み関数....................................................... 260■ 12.4.1

構文一覧……262 ／■ 12.4.2

解説……267

12.5 ブレンド組込み関数..................................................................................... 271■ 12.5.1

構文一覧……271 ／■ 12.5.2

解説……272

12.6 ビット単位演算系命令................................................................................. 273■ 12.6.1

構文一覧……276 ／■ 12.6.2

解説……286

12.7 ブロードキャストスカラー系組込み関数.................................................... 292■ 12.7.1

構文一覧……293 ／■ 12.7.2

解説……295

12.8 比較系命令系組込み関数.............................................................................. 300■ 12.8.1

構文一覧……303 ／■ 12.8.2

解説……30912.9 コンプレス系命令系組込み関数................................................................... 314■ 12.9.1

構文一覧……315 ／■ 12.9.2

解説……316

12.10 変換系組込み関数......................................................................................... 320■ 12.10.1

構文一覧……324 ／■ 12.10.2

解説……330

12.11 拡張／ロード操作の組込み関数................................................................... 334■ 12.11.1

構文一覧……335 ／■ 12.11.2

解説……337

12.12 集約と分散操作の組込み関数...................................................................... 340■ 12.12.1

構文一覧……342 ／■ 12.12.2

解説……346

12.13 挿入／抽出操作の組込み関数...................................................................... 347■ 12.13.1

構文一覧……348 ／■ 12.13.2

解説……353

12.14 ロード／ストア操作の組込み関数............................................................... 357■ 12.14.1

構文一覧……359 ／■ 12.14.2

解説……363

12.15 その他の操作の組込み関数.......................................................................... 368■ 12.15.1

構文一覧……369 ／■ 12.15.2

解説……374

12.16 移動操作の組込み関数................................................................................. 377■ 12.16.1

構文一覧……378 ／■ 12.16.2

解説……380

12.17 パック／アンパック演算の組込み関数....................................................... 382■ 12.17.1

構文一覧……383 ／■ 12.17.2

解説……385

12.18 置換操作の組込み関数................................................................................. 388■ 12.18.1

構文一覧……389 ／■ 12.18.2

解説……39312.19 リデュース組込み関数................................................................................. 397■ 12.19.1

構文一覧……398 ／■ 12.19.2

解説……40212.20 設定操作の組込み関数................................................................................. 404■ 12.20.1

構文一覧……405 ／■ 12.20.2

解説……408

12.21 シャッフル組込み関数................................................................................. 410■ 12.21.1

構文一覧……410 ／■ 12.21.2

解説……41312.22 テスト操作の組込み関数.............................................................................. 416■ 12.22.1

構文一覧……416 ／■ 12.22.2

解説……418xi

12.23 型キャスト操作の組込み関数...................................................................... 421■ 12.23.1

構文一覧……421 ／■ 12.23.2

解説……423

12.24 マスク操作の組込み関数.............................................................................. 425■ 12.24.1

構文一覧……425 ／■ 12.24.2

解説……426

12.25 SVML 操作の組込み関数............................................................................ 428■ 12.25.1■ 12.25.3■ 12.25.5■ 12.25.6■ 12.25.7■ 12.25.8

除算の組込み関数……428 ／■ 12.25.2 誤差関数操作の組込み関数……435
指数演算の組込み関数……438 ／■ 12.25.4 対数演算の組込み関数……442
逆数演算の組込み関数……447
平方根演算の組込み関数……453
丸め操作の組込み関数……457
三角関数操作の組込み関数……462

参考文献、参考サイト、参考資料........................................................................................ 472

索  引...................................................................................................................................... 473

xii

第1章

SIMD 概論

第1章

1

SIMD 概論

コンピューターの性能を向上させるためには、CPU コアを多数用意する、アクセラレータを利用しオフロードする、CPU クロック数を高速化するなどの手法があります。SIMD（Single
Instruction Multiple Data）は、それらとは思想が異なり、1 つの命令で複数のデータを処理する考えです。近年の CPU は SIMD 命令を装備しており、それらのビット幅や命令は日々拡張されつつあります。
SIMD に対抗するアーキテクチャに SISD（Single Instruction Single Data）があります。これは、
1 つの命令で 1 つのデータを処理します。これに対し SIMD 命令は、複数のデータを、1 つの命令で処理します。つまりデータ並列の一種と考えて良いでしょう。一般にデータ並列は、配列などのデータを複数のスレッドで並列実行しますが、SIMD 命令では 1 つの命令で同時に複数の演算を実行します。
最近のコンパイラは自動ベクトル化をサポートしつつあり、自動で SIMD 命令を出力するものもあります。しかし、標準化やコンパイラの対応は途上です。コンパイラの自動ベクトル化は長いこと研究されていますが、人間が最適化するレベルに達するには、まだ多くの時間を必要とするでしょう。

1

1

SIMD 概論

1.1 フリンの分類
コンピューターの分類はさまざまな観点からなされていますが、ここでは命令とデータの並列化を体系的にまとめたフリンの分類（Flynn’s taxonomy）を示します。これは、マイケル・J・フリン（Michael J. Flynn）が 1966 年に提案したコンピューターアーキテクチャの分類法です。フリンが定義した 4 つの分類は、命令の並列度とデータストリームの並列度に基づくものです。
● SISD: Single Instruction, Single Data stream● SIMD: Single Instruction, Multiple Data streams● MISD: Multiple Instruction, Single Data stream● MIMD: Multiple Instruction, Multiple Data streams

以降に分類の概念図を示します。
プロセッシングユニット（Processing Unit）

命令

命令

データ

データ

命令

データ

データ

命令

図1.1●フリンの分類による4つの並列化の動作モデル

SISD

1 つの命令で 1 つのデータに対して処理を行う命令形式のことです。最も基本的なアーキテクチャで、命令にもデータにも並列性のない逐次的な方式です。旧式のパー
ソナルコンピューターや古いメインフレームで採用されています。

SIMD

2

1 つの命令で複数のデータに対して処理を行う命令形式のことです。SISD と異なり、

1.2

SIMD 概要

SIMD ではプロセッサー内に演算を実行する実行ユニットが複数用意されており、そ1

れぞれが異なるデータに対し並列に演算を行います。SIMD では読み取られた命令が各実行ユニットにブロードキャストされ、各実行ユニットが持つデータに対して同一2の演算が実行されます。
MISD

3

日本語では複数命令単一データ流などと訳されます。このモデルでは、複数のプログ
ラムカウンタが示す命令が単一のデータに対して適用されます。このモデルは他のも4のに比べ若干特殊なので、実際にどういったアーキテクチャがこのモデルに属するかといった点に関しては計算機アーキテクチャの教科書に譲ります。
MIMD

5

独立した複数のプロセッサーを持ち、それぞれのプロセッサーは異なる命令を

6

使 っ て 異 な る デ ー タ を 処 理 し ま す。MIMD ア ー キ テ ク チ ャ は さ ま ざ ま な 分 野 で応用されています。一般に分散システムは MIMD 型であると言われ、単一の共7

有メモリーを使う場合と、分散メモリーを使う場合があります。近年、一般的になってきたメニーコア CPU と SIMD の組み合わせは MIMD の一種と考えて良いで

8

しょう。

9
10

1.2 SIMD 概要

11

細かな説明の前に、SIMD の概要を説明しましょう。例えば、

12c[0] = a[0] + b[0]c[1] = a[1] + b[1]c[2] = a[2] + b[2]c[3] = a[3] + b[3]のような処理が必要だったとします。これを従来の命令セット（SISD 命令）で処理する場合、順
番に 1 つずつ処理しなければなりません。

3

1

SIMD 概論

①

c[0]

=

a[0]

+

b[0]

②

c[1]

=

a[1]

+

b[1]

③

c[2]

=

a[2]

+

b[2]

④

c[3]

=

a[3]

+

b[3]

図1.2●従来の命令セットでの処理

ところが SIMD 命令を使用すれば、1 つの命令で 4 つのデータを処理できます。
c[0]
①

c[1]
c[2]
c[3]

a[0]

=

a[1]
a[2]

b[0]

+

a[3]

b[1]
b[2]
b[3]

図1.3●SIMD命令での処理

この機能を利用すると行列計算、3D 座標計算やデジタルフィルター処理などを高速に処理することができます。現代では汎用のプロセッサーに留まらず、GPU や組込み用などのプロセッサー
でも SIMD 命令がサポートされています。

1.3 SIMD 命令で扱うデータ形式
現在使われているインテル社のプロセッサーは、初期のものを除き SIMD 命令を装備しています。初期の MMX 命令は最初に現れた SIMD 命令です。MMX 命令は、64 ビットの整数演算を実行できます。ただし、MMX 命令は FPU 命令と混在して使用するときに注意が必要です。現在は、MMX 命令を使う機会はないでしょう。すでに AVX 命令をサポートしたプロセッサーが普通になりました。MMX 命令は 64 ビット長のデータ、SSE 命令以降は 128 ビット長のデータ、AVX 命令
は 256 ビット長、そして AVX-512 命令は 512 ビット長のデータを 1 つの命令で処理します。この固定長データに同じ型の複数のデータを格納し処理します。このように複数のデータをひとか

4

1.4

SIMD 命令とはたまりで扱う場合、これをパックされたデータと呼びます。

1
2

1.4 SIMD 命令とは

3
4SIMD 命令セットには、米 Intel 社の SSE/AVX 命令セットなど以外に、米 Motorola 社の AltiVecや米 Sun Microsystems 社（後の Oracle 社）の VIS（Visual Instruction Set）などが広く知られて5います。組込み用途やモバイル機器に多く採用される米 ARM 社の ARM プロセッサーや、ルネサ
ステクノロジ社の SH-5 でも SIMD 命令がサポートされており、組込み分野でも利用が進みつつあ6

ります。他にも Sony Computer Entertainment の Cell プロセッサーの SIMD も良く知られています。
SIMD とは、Single Instruction Multiple Data の略です。これに対し、通常のノイマン型計算機

7における非 SIMD 命令は SISD（Single Instruction Single Data）に分類されます。命名から分かる
ように、SIMD 命令では同時に複数のデータを扱うことができます。SIMD 命令は単位時間あたり

8の処理データ量を増加させるための技術です。

9
10
11
12

5

2

第2章

はじめての.
プログラム

第2章

はじめてのプログラム

細かな SIMD の説明に入る前に、SIMD の概要を理解するため、簡単なプログラムを使用して説明します。ここでは、実際のプログラミングで頻繁に用いられる、ループの部分を SIMD 命令へ置き換えるプログラムを紹介します。紹介するプログラムは、短い 2 つの 1 次元配列を加算します。以降に、プログラムの概要を示します。

c

=

a

＋

b

図2.1●プログラムの概要

2.1 C++ 言語で記述
まず SIMD 命令を使用せず C++ 言語で記述した通常のプログラムを示します。float 型配列 aと b の各要素を加算し、float 型の配列 c へ格納します。配列の要素数は AVX-512 命令を考慮し16 とします。AVX-512 命令で使用するレジスターは 512 ビットです、float 型（単精度浮動小数点）は 32 ビットですので、512 ÷ 32 = 16 より、要素数を 16 として C++ と SIMD 命令でプロ

7

2

はじめてのプログラム

グラムの変化を観察します。以降に、C++ 言語で記述したソースリストを示します。
リスト2.1●ソースリスト（cpp.cpp）#include <iostream>#include <iomanip>
int main(void){

const size_t Length = 16;

float a[Length], b[Length], c[Length];//512bits = float[16]for (int i = 0; i < Length; i++){}a[i] = (float)i;b[i] = (float)i + (float)i / 10.0f;

for (int i = 0; i < Length; i++){}c[i] = a[i] + b[i];std::cout.setf(std::ios::right);
for (int i = 0; i < Length; i++){std::cout << std::fixed << std::setprecision(2)<< std::setw(6) << a[i] << " + "<< std::setw(6) << b[i] << " = "}}<< std::setw(6) << c[i] << std::endl;

return 0;

for 文を使用し、要素分だけ繰り返します。まず、Windows 上でビルドする方法を示します。

Visual Studio がインストールされていることを前提とします。

8

2.1

C++ 言語で記述

Visual Studio のプロジェクトを作成しても良いのですが、デバッグが必要とは思われないので

1

コマンドラインでビルドします。Visual Studio をインストールするとスタートメニューに「x64Native Tools Command Prompt Visual Studio 2019」が追加されますので、それを使用します。

2

C:¥>cl /EHsc cpp.cpp

3

あるいは、

4

C:¥>cl /EHsc /Fe:cpp cpp.cpp

56と入力すると実行ファイルが生成されます。コマンドプロンプトを開くのにメニューを探すのが面倒な時は、Windows キーを押して、その後「x64」と入力すると「x64 Native Tools Command

7

Prompt Visual Studio 2019」が現れますので、Enter キーを押して起動してください。
ビルドを便宜的にルートで実行していますが、作業フォルダー内で行う方が良いでしょう。以8

降に、実行結果を示します。
C:¥>cpp0.00 +

0.00 =

0.00

2.00 +

2.20 =

4.20

1.00 +
3.00 +
4.00 +
5.00 +
6.00 +
7.00 +
8.00 +
9.00 +

1.10 =
3.30 =
4.40 =

9.90 =

12.00 +

13.20 =

14.00 +
15.00 +

11

12.60

12

14.70

11.00 =

13.00 +

8.40

7.70 =
8.80 =
12.10 =
14.30 =
15.40 =
16.50 =

10

6.30
10.50

10.00 +
11.00 +

2.10

5.50 =
6.60 =

9

16.80
18.90
21.00
23.10
25.20
27.30
29.40
31.50

9

2

はじめてのプログラム

2.2 SSE 命令で記述
SIMD 命令の最初の例として、SSE 命令を使用する例を示します。SSE 命令は 128 ビットですので、float 型なら 4 要素を 1 回の命令で処理できます。以降に、SSE 命令で記述したソースリス
トを示します。

リスト2.2●ソースリスト（sse.cpp）#include <iostream>#include <iomanip>#include <xmmintrin.h>using namespace std;//inittemplate <typename T>

void initData(T a[], T b[], const size_t Length){

for (int i = 0; i < Length; i++){}a[i] = (float)i;b[i] = (float)i + (float)i / 10.0f;}// print

template <typename T>

void printData(T a[], T b[], T c[], const size_t Length){cout.setf(ios::right);

for (int i = 0; i < Length; i++){cout << fixed << setprecision(2)<< setw(6) << a[i] << " + "<< setw(6) << b[i] << " = "}10<< setw(6) << c[i] << endl;

2.3

SSE 命令をインラインアセンブリで記述}

1//mainint main(void){

const size_t Length = 16;2//512bits = float[16]3const size_t numOffloats = sizeof(__m128) / sizeof(float);__m128 a[Length / numOffloats], b[Length / numOffloats];
float

4

c[Length];

initData((float*)a, (float*)b, Length);5for (int i = 0; i < Length / numOffloats; i++)6{}__m128 ps = _mm_add_ps(a[i], b[i]);// add7_mm_storeu_ps((float*)&c[i * numOffloats], ps); // store

8

printData((float*)a, (float*)b, c, Length);}

return 0;910配列の初期化と、結果の表示は以降のプログラムでも同じコードとなるため、この部分をinitData 関数と printData 関数とします。扱うデータの型が変わる可能性もありますので、

11

網掛け部分が先のプログラムと異なる点です。SSE 命令対応のイントリンシックを使用します12

template とします。

ので、xmmintrin.h をインクルードします。実際の加算は、_mm_add_ps で処理します。これを 1回実行することで 4 つの要素が処理されます。実行結果は、先と同じです。

2.3 SSE 命令をインラインアセンブリで記述
先のプログラムはイントリンシックを使用しました。ここでは、インラインアセンブリで記述する例を紹介します。Visual Studio の 64 ビット（x64）
はインラインアセンブリを許していません。

11

2

はじめてのプログラム

このため、本プログラムに限っては Visual Studio のメニューで［x86］を選ぶか、「x86 Native
Tools Command Prompt for VS 2019」のコマンドラインでビルドしてください。以降に、SSE 命
令をインラインアセンブリで記述したソースリストを示します。
リスト2.3●ソースリストの一部（sseAsm.cpp）
︙

initData(a, b, Length);
__asm
{

mov
xor

loop1 :

esi, esi

movupsxmm0, a[esi]addpsxmm0, xmm1

movupsmovups
add
}

cx, 4

loopnz; 16bytes x 4times

xmm1, b[esi]c[esi], xmm0
esi, 16loop1; float* 4printData(a, b, c, Length);

︙

先のプログラムと共通の部分が多いため異なる部分のみ示します。SSE 命令は 128 ビットです

ので、float 型なら 4 要素を 1 回で処理できるため、4 回で 512 ビットを処理できます。繰り返

す回数を cx レジスターへ格納し、esi レジスターへデータをアクセスするためのオフセットを格納します。movups 命令で単精度浮動小数点配列 a の 4 要素を xmm0 レジスターへ、単精度浮動小数点配列 b の 4 要素を xmm1 レジスターへロードします。次に、addps 命令で 4 要素を同時に加算します。加算結果は xmm0 レジスターへ上書きされます。この xmm0 レジスターの内容を movups 命

令で変数 c へ格納します。最後に、esi レジスターのオフセットを更新し、loopnz で 4 回のループが終わっていなければ loop1 のラベル位置へジャンプし次のデータを処理します。実行結果は、先と同じです。

64 ビット環境で、アセンブリ言語で記述する方法は、第 5 章「アセンブリ言語で記述」で示し
ます。

12

2.4

AVX 命令で記述

1

2.4 AVX 命令で記述

2

AVX 命令で記述したプログラムを示します。AVX 命令は 256 ビットですので、float 型なら 83

要素を 1 回の命令で加算できます。以降に、AVX 命令で記述したソースリストを示します。

4

リスト2.4●ソースリストの一部（avx.cpp）
︙#include <immintrin.h>5

using namespace std;6// main7

︙

int main(void){

const size_t Length = 16;8// 512bits = float[16]const size_t numOffloats = sizeof(__m256) / sizeof(float);__m256 a[Length / numOffloats], b[Length / numOffloats];
float

9

c[Length];

10

initData((float*)a, (float*)b, Length);11for (int i = 0; i < Length / numOffloats; i++){}__m256 ps = _mm256_add_ps(a[i], b[i]);_mm256_storeu_ps((float*)&c[i * numOffloats], ps);// add12// storeprintData((float*)a, (float*)b, c, Length);}

return 0;

AVX 命令は 256 ビットですので、float 型なら 8 要素を 1 回で処理できます。AVX 命令対応のイントリンシックを使用しますので、immintrin.h をインクルードします。実際の加算は、_mm256_add_ps で処理されます。これを 1 回実行することで 8 つの要素が処理されます。イント

リンシックをアセンブリ言語で記述した例を以降に示します。

13