■サンプルファイルのダウンロードについて
本書掲載のサンプルファイルは、一部を除いてインターネット上のダウンロードサービスからダウンロードすることができます。詳しい手順については、本書の巻末にある袋とじの内容をご覧ください。
なお、ダウンロードサービスのご利用にはユーザー登録と袋とじ内に記されている番号が必要です。そのため、本書を中古書店から購入されたり、他者から貸与、譲渡された場合にはサービスをご利用いただけないことがあります。あらかじめご承知おきください。
● ファイルサイズを小さくするため、NuGet で導入した packages をプロジェクトから削除しています。
● ファイルサイズを小さくするため、コンパイル後のバイナリファイルは削除しています。
● ソリューションファイルやプロジェクトファイルなどは、そのまま利用できることを保証するものではありません。
● ダウンロードファイルの提供は、ソースコードを入力する手間を省くことを目的としています。

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

はじめに
近年において、ソフトウェアエンジニアがハードウェアのアーキテクチャを意識する機会は激減しました。それは悪いことではないでしょう。ソフトウェアエンジニアがハードウェアを意識していては、開発したソフトウェアはハードウェア依存となりポータビリティやスケーラビリティなどを失ってしまいます。特に抽象化された環境では、ほぼハードウェアアーキテクチャーを意識する必要はありません。
しかしながら、仮想マシン（バーチャルマシン）やフレームワーク上の開発言語を使用している最中に、大量の演算処理を行う必要に迫られる場合があります。一般的には、言語の実装を工夫するだけで所望の性能を獲得できる場合が少なくありません。しかし、実装の工夫だけでは目的の性能を達成できない場合もあります。本書では、C# を利用していながら、比較的大規模な数値演算を行いたときに重宝するベクトル処理について解説を行います。また、副産物として C# の性能評価についても触れます。

C# は x86 系 CPU の SIMD 命令を利用できるように、System.Runtime.Intrinsics.X86 クラスなどで SIMD 命令をサポートするメソッドが提供されています。このクラスは、C++ のイントリンシックと対応したメソッドを用意しています。ベクトル処理に慣れていない人でも、これらのメソッドを利用すると、容易に C# でベクトルプログラミングを行うことができるでしょう。これによって、C# 単体で、リッチな UI と高速な数値演算両方を満足するプログラムを開発できる可能性が高くなります。
本書は、以下のような人を対象読者としています。
● C# でベクトルプログラミングを習得したい人● ベクトル処理を理解したい人● C# で演算の多いプログラムを開発したい人

本書を参考に C# におけるベクトル処理、SIMD 命令、そして性能向上したソフトウェアの開発に役立ててください。微力ながら本書が学習の助けになれば幸いです。
謝辞
出 版 に あ た り、 開 発 環 境 で あ る Visual Studio を 無 償 公 開 し て い る 米 Microsoft 社、BenchmarkDotNet を無償公開している .NET Foundation、ほか各種環境を提供している団体・個人に深く感謝いたします。ならびに、株式会社カットシステムの石塚勝敏氏にも深く感謝いたします。
2022 年初春 都立東大和南公園にて 北山洋幸

iii

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

https://docs.microsoft.com/ja-jp/dotnet/api/system.runtime.intrinsics.x86?view=

2.

Intel® Advanced Vector Extensions Programming Reference JULY 20093.

Introduction to Intel® Advanced Vector Extensions, By Chris Lomont

4.

Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 1: Basic

net-6.0 / System.Runtime.Intrinsics.X86 のリファレンス

Architecture, December 2021
5.

Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 2A, 2B,

6.

Intel® 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 3A, 3B,

2C, and 2D: Instruction Set Reference, A-Z, September 2016
3C, and 3D: System Programming Guide, December 2021
7.

Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 4: Model-specificRegisters, December 2021

8.
9.

https://benchmarkdotnet.org / BenchmarkDotNet 公式サイト

インテル ® 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マ
ニュアル , 2011 年 4 月

10.

IA-32 インテル ® アーキテクチャ ソフトウェア・デベロッパーズ・マニュアル / 上巻：基本アーキテクチャ（資料番号 253665-013J）, 中巻 A：命令セット・リファレンス A-M（資料番号 253666-013J）, 中巻 B：命令セット・リファレンス N-Z（資料番号 253667013J）, 下巻：システム・プログラミング・ガイド（資料番号 253668-013J）

11.

北山洋幸、
『AVX 命令入門』、カットシステム

12.

北山洋幸、
『512 ビット・ベクトルプログラミング入門』、カットシステム

13.

3D Vector Normalization Using 256-Bit Intel® Advanced Vector Extensions (Intel®

AVX / https://www.intel.com/content/dam/develop/external/us/en/documents/

normvec-181650.pdf / Stan Melax 著

本書の使用にあたって開発環境、および、実行環境の説明を行います。
● Windows バージョン

Windows のバージョンへ依存するとは思えませんが、本書のプログラムの開発および動作確
認は Windows 10 Pro（64 ビット）で行いました。

iv

● Visual C# のバージョンとエディション

無償の Visual Studio Community 2022 を使用します。
● 推奨 CPU

推奨 CPU は、AVX2 命令をサポートしているものです。ただし、AVX のみをサポートしている CPU でも一部書き換えれば問題なく利用できます。AVX 命令をサポートしている CPU は、Sandy Bridge 以降であり、利用可能となった時期は 2011 年頃からです。AVX2 命令をサポートしている CPU は、Haswell 以降であり、利用可能となった時期は 2013 年頃からです。
● URL

URL の記載がある場合、執筆時点のものであり変更される可能性もあります。リンク先が存在しない場合、キーワードなどから自分で検索してください。
● メソッドの説明

C# のクラスは、たくさんのオーバーロードを提供しています。本書のメソッド説明は紙面の関係で一部のオーバーロードの説明に限られます。すべてを知りたい場合は、参考サイトを参照してください。

用語
用語の使用に関して説明を行います。
● カタカナ語の長音表記「メモリー」や「フォルダー」など、最近は語尾の「ー」を付けるのが一般的になっています
ので、なるべく「ー」を付けるようにします。ただ、開発環境やドキュメントなどに従来の用語を使用している場合も多く、参考資料も混在して使用しているため、本書では、語尾の「ー」は統一していません。
● ソースリストとソースコード基本的に同じものを指しますが、ソースリストと表現する場合ソース全体を、ソースコードと表現する場合ソースの一部を指す場合が多いです。
● SIMD、AVX、ベクトル命令

これらを混在して使用しています。System.Runtime.Intrinsics.X86 クラスのメソッドのことを、SIMD 命令、SIMD メソッド、AVX メソッドと表現する場合もあります。特別な意味をv持って使い分けていませんので、文脈から何を指しているか読み取ってください。
● 命令、メソッド

SIMD 命 令 や AVX 命 令 と 記 載 し て い る 場 合 が あ り ま す が、 実 際 は System.Runtime.
Intrinsics.X86 クラスのメソッドなどです。

● イントリンシック

System.Runtime.Intrinsics.X86 クラスのメソッドなどをイントリンシックと記載している場合があります。ほとんどの System.Runtime.Intrinsics.X86 クラスのメソッドは C++ のイントリンシックと対応しています。メソッドの説明で理解が進まない場合、C++ のイントリンシックの説明を参照するのも良い方法です。
● Single と float

単精度浮動小数点を指します。公式なドキュメントが混在して使用していましたので、それに倣いました。Double と double や Int32 と int なども同様です。

● タスクとスレッド

C# では、Task クラスを使用してスレッドを起動するときがあります。このため、スレッドとタスク、そして Task を混在して使用します。両方とも同じものを指しますが、スレッドをタスクと表現する場合があり、その逆もあります。

vi

目次

目次
はじめに.......................................................................................................................... iii

第1章

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

1.1

フリンの分類....................................................................................................... 21.2

SIMD 概要........................................................................................................... 41.3

SIMD 命令で扱うデータ形式............................................................................. 5

1.4

SIMD 命令とは.................................................................................................... 61.5

C# と SIMD........................................................................................................ 7System.Runtime.Intrinsics 名前空間..........................................................................7System.Runtime.Intrinsics.X86 名前空間................................................................7
基本的な使用法...................................................................................................................8第2章

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

一般的な C# で記述..........................................................................................102.2

C# のベクトルクラスで記述............................................................................112.3fixed ステートメント.......................................................................................13

2.4

stackalloc ステートメント.............................................................................15

2.5

stackalloc ステートメントと fixed ステートメントの使い分け.................16

2.6

C# で SIMD メソッドを利用する手続き.........................................................17

2.7

Sse メソッドと Avx2 メソッド......................................................................18
Sse メソッドの例............................................................................................................18Avx2 メソッドの例.........................................................................................................20

2.8

使用できるクラスの判断...................................................................................21
CpuId を利用する...........................................................................................................23

2.9

アライメント.....................................................................................................27vii

目 次第3章
3.1

各メソッド.............................................................. 29
算術演算系メソッド..........................................................................................29
加算系メソッド................................................................................................................29乗除算系メソッド...........................................................................................................37
その他の演算系メソッド.............................................................................................40

3.2

ビット単位演算系メソッド...............................................................................433.3

条件付き混合または条件付きマージ系メソッド..............................................523.4

ベクトル比較系メソッド...................................................................................56

3.5

変換系メソッド..................................................................................................61

3.6

最大値と最小値を特定するメソッド................................................................68

3.7

その他の操作系メソッド...................................................................................73

第4章

簡単な応用例 .......................................................... 91

4.1

2 つの配列を加算..............................................................................................91

4.2

2 つの配列を乗算..............................................................................................95

4.3

長大な一次元配列に定数を乗ずる....................................................................964.4

配列の水平演算............................................................................................... 101
単純な総和を求める.................................................................................................... 101
長い配列の総和を求める.......................................................................................... 103

4.5

第5章

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

5.1

AOS と SOA ................................................................................................ 113

5.2

AOS から SOA へのシャッフル................................................................... 115

5.3

256 ビットの AOS と SOA......................................................................... 121

5.4

一般化したメソッド....................................................................................... 125

第6章

viii

配列の最大値と最小値.................................................................................... 107

ベンチマーク ........................................................ 131

6.1

Stopwatch クラス....................................................................................... 131

6.2

BenchmarkDotNet.................................................................................... 136

6.3乗算................................................................................................................. 142

6.4

一次元配列に定数を乗ずる............................................................................ 143

6.5

長い配列の総和を求める................................................................................ 146

第7章

ヘルパークラス ..................................................... 149

7.1

配列同士の加算（1）...................................................................................... 149

7.2

配列同士の加算（2）...................................................................................... 154

7.3

配列同士の乗算............................................................................................... 156

7.4

長大な一次元配列に定数を乗ずる................................................................. 157

7.5

長い配列の総和を求める................................................................................ 1597.6

ヘルパークラスの拡張.................................................................................... 160

第8章

ヘルパークラスとベンチマーク ............................... 167

8.1

加算................................................................................................................. 167

8.2

乗算................................................................................................................. 170

8.3

一次元配列に定数を乗ずる............................................................................ 172

8.4

長い配列の総和を求める................................................................................ 174

8.5

ヘルパークラスと性能.................................................................................... 176

第9章

BenchmarkDotNet ............................................ 177

9.1

引数を即値で指定........................................................................................... 178

9.2

引数を IEnumerable で指定........................................................................ 180

9.3引数が配列の例............................................................................................... 182

9.4

配列引数を IEnumerable で指定................................................................ 184

9.5

乗算................................................................................................................. 186

9.6

一次元配列に定数を乗ずる............................................................................ 189

9.7

長い配列の総和を求める................................................................................ 192

9.8

配列の最大値と最小値.................................................................................... 194ix

目 次

付録

APPENDIX - SIMD と並列化 ................................... 197

A.1

逐次プログラム............................................................................................... 197

A.2

並列プログラム............................................................................................... 200

A.3

ベンチマーク.................................................................................................. 202

A.4

並列プログラム・Task.Run......................................................................... 204

A.5

配列タスクで並列化....................................................................................... 206

索  引.......................................................................................................................... 211

x

第1章

SIMD 概論

第1章

1

SIMD 概論

抽象的な環境でも性能を問われる場合が少なくありません。C# を使用し生産性を向上させながら、性能も向上させたいときがあるでしょう。C# で性能を向上させたければ並列処理などがサポートされており、CPU コアを最大限有効活用する方法は従来から使用されています。それに加え System.Runtime.Intrinsics.X86 クラスなどを利用することによってベクトルプログラミングが可能となっています。本書では、あまり説明が行われていないベクトルプログラミングを解説します。
コンピューターの性能向上を実現するには、（1）CPU コアを多数用意する（2）アクセラレータを利用する（3）CPU クロック数を高速化するなどの手法があります。ベクトル処理（SIMD: Single Instruction Multiple Data）は、それらとは思想が異なり、1 回の処理で複数のデータを処理する考えです。近年の CPU は SIMD 命令を装備しており、それらのビット幅や命令は日々拡張されています。
付録

SIMD に対抗するアーキテクチャに SISD（Single Instruction Single Data）があります。これは、一度に 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つの並列化の動作モデル

2

1.1

フリンの分類● SISD

1

1 つの命令で 1 つのデータに対して処理を行う命令形式のことです。最も基本的なアーキテクチャで、命令にもデータにも並列性のない逐次的な方式です。旧式のパーソナルコン

2ピューターや古いメインフレームで採用されています。
● SIMD

3

1 つの命令で複数のデータに対して処理を行う命令形式のことです。SISD と異なり、SIMD
ではプロセッサ内に演算を実行する実行ユニットが複数用意されており、それぞれが異なる4データに対し並列に演算を行います。SIMD では読み取られた命令が各実行ユニットにブロードキャストされ、各実行ユニットが持つデータに対して同一の演算が実行されます。

5● MISD

日本語では複数命令単一データ流などと訳されます。このモデルでは、複数のプログラムカ

6ウンタが示す命令が単一のデータに対して適用されます。このモデルは他のものに比べ若干特殊なので、実際にどういったアーキテクチャがこのモデルに属するかといった点に関して7は計算機アーキテクチャの教科書に譲ります。
● MIMD

8

独立した複数のプロセッサを持ち、それぞれのプロセッサは異なる命令を使って異なるデータを処理します。MIMD アーキテクチャはさまざまな分野で応用されています。一般に分散9システムは MIMD 型であると言われ、単一の共有メモリを使う場合と、分散メモリを使う場合があります。

付録

31SIMD 概論

1.2 SIMD 概要
細かな説明の前に、SIMD の概要を説明しましょう。例えば、単精度浮動小数点型の配列があり、それらを加算する処理があったとします。以降に、一般的な方法で記述した疑似コードを示します。
float[] a = { … };float[] b = { … };var c = new float[8];c[0] = a[0] + b[0]c[1] = a[1] + b[1]c[2] = a[2] + b[2]c[3] = a[3] + b[3]c[4] = a[4] + b[4]c[5] = a[5] + b[5]c[6] = a[6] + b[6]c[7] = a[7] + b[7]処理を以降に示します。加算処理を要素ごと順番に 1 つずつ処理しなければなりません。
①c[0]=a[0]+b[0]②c[1]=a[1]+b[1]③c[2]=a[2]+b[2]

④c[3]

=

a[3]+b[3]⑤c[4]=a[4]+b[4]⑥c[5]=a[5]+b[5]⑦c[6]=a[6]+b[6]

⑧c[7]

=

a[7]+b[7]図1.2●一般的な方法

4

1.3

SIMD 命令で扱うデータ形式

この処理を SIMD で記述する例を、以降に疑似コードで示します。

1

float[] a = { … };float[] b = { … };2c = Avx2.Add(a, b);3System.Runtime.Intrinsics.X86 の Avx クラスの Add メソッドを利用すると、一度に配列の 84var c = new float[8];要素をそれぞれ独立して加算できます。
c[0]

a[0]

b[0]

c[2]

a[2]

b[2]

c[1]
c[3]
c[4]
c[5]
c[6]
c[7]

a[1]

=a[3]
a[4]
a[5]
a[6]
a[7]

5b[1]

+b[3]
b[4]

6
一回で処理

7b[5]
b[6]

8b[7]

9

図1.3●SIMDの処理

この機能を利用すると行列計算、3D 座標計算やデジタルフィルタ処理などを高速に処理することができます。

1.3 SIMD 命令で扱うデータ形式
付録

現在使われているプロセッサは、例外を除き SIMD 命令を装備しています。パソコンで使用されているプロセッサは、ほとんど例外なく SIMD 命令を実装しています。インテル系のプロセッ
サは、初期の MMX 命令から始まり、現在では AVX-512 命令を実装しています。MMX 命令は制限も多かったですが、既に過去のものとなりました。現在現役で使われているプロセッサは例外を除き AVX2 命令をサポートしているのが普通です。近年購入したパソコンであれば、AVX-5125

1SIMD 概論命令もサポートしているでしょう。ただ、原稿執筆時点では、C# が AVX-512 をサポートしたクラスを提供していませんでした。このため、本書では AVX2 までの例題を示すこととします。
MMX 命令は 64 ビット長のデータ、
SSE 命令以降は 128 ビット長のデータ、
AVX 命令は 256 ビット長、そして AVX-512 命令は 512 ビット長のデータを 1 つの命令で処理します。この固定長データに同じ型の複数のデータを格納し処理します。このように複数のデータをひとかたまりで扱う場合、これをパックされたデータと呼びます。

1.4 SIMD 命令とはSIMD 命令セットには、米 Intel 社の SSE/AVX 命令セットなど以外に、米 Motorola 社の AltiVecや米 Sun Microsystems 社（後の Oracle 社）の VIS（Visual Instruction Set）などが広く知られています。組込み用途やモバイル機器に多く採用される米 ARM 社の ARM プロセッサや、ルネサステクノロジ社の SH-5 でも SIMD 命令がサポートされており、組込み分野でも利用が進みつつあります。他にも Sony Computer Entertainment の Cell プロセッサの SIMD も良く知られています。
SIMD とは、Single Instruction Multiple Data の略です。これに対し、通常のノイマン型計算機における非 SIMD 命令は SISD（Single Instruction Single Data）に分類されます。命名から分かるように、SIMD 命令では同時に複数のデータを扱うことができます。本書で解説する SIMD 命令は、単位時間あたりの処理データ量を増加させるための技術です。
本書では、C# で SIMD を解説しますので、SIMD やベクトル命令と表現した場合、インテル系プロセッサがサポートする SIMD 命令を指します。

6

1.5

C# と SIMD

1.5 C# と SIMD

12SIMD の概要を理解できたところで、C# の SIMD を説明します。C# で SIMD を利用したい場合

は System.Runtime.Intrinsics と System.Runtime.Intrinsics.X86 を利用します。

3

System.Runtime.Intrinsics 名前空間

4

命令セット拡張で使用するさまざまなサイズや形式で、レジスタの状態を作成および伝達するために使用される型が含まれています。

5

System.Runtime.Intrinsics.X86 名前空間

6x86 および x64 システムの選択命令セット拡張機能を公開します。 これらの命令セットは、各拡張機能の個別クラスとして表現されます。現在の環境内の任意の拡張機能のサポートは、それ7ぞれの型の IsSupported プロパティに対してクエリを実行することで判断できます。執筆時点では、AVX2 までがサポートされており、AVX-512 は未サポートです。

8

本クラスがサポートしている、X86 および x64 システムの選択命令セット拡張機能の代表的な一覧を示します。

9

表1.1●クラスとサポートする命令セット
クラス名
Avx

Avx2
Fma
Sse

Sse2
Sse3

Sse41
Ssse3

X86BaseIntel AVX ハードウェア命令にアクセスできます。
Intel AVX2 ハードウェア命令にアクセスできます。
Intel FMA ハードウェア命令にアクセスできます。
Intel SSE ハードウェア命令にアクセスできます。
Intel SSE2 ハードウェア命令にアクセスできます。
Intel SSE3 ハードウェア命令にアクセスできます。
Intel SSE4.1 ハードウェア命令にアクセスできます。
Intel SSE4.2 ハードウェア命令にアクセスできます。
付録

Sse42

説明

Intel SSSE3 ハードウェア命令にアクセスできます。
x86 基本ハードウェアの命令にアクセスできます。

7

1

SIMD 概論

基本的な使用法
各種クラスが用意され、Vector 構造体を基本にして様々な計算処理を提供しています。Vector構造体もビット長に応じて 64、128、256 と 3 種類が提供されています。

82第2章はじめての.
プログラム

第2章

はじめてのプログラム

まず、簡単なプログラムを使用して説明します。ここでは、一次元配列同士を加算し、結果を一次元配列へ格納するプログラムを例として用います。以降に、プログラムの処理概要を示します。
c=a+b

図2.1●プログラムの処理概要

C# で一般的に記述する場合、for ループを用いて 1 要素ずつ処理します。System.Runtime.

Intrinsics クラスや System.Runtime.Intrinsics.X86 クラスを使用すると、一度に複数の要素を処理できます。

本章では、一般的な C# で記述したものと、前記クラスを使用して実装したものを示し、それぞれの違いを理解できるように努めます。

9

2

はじめてのプログラム

2.1 一般的な C# で記述
ごく普通に C# で記述したプログラムを示します。float 型配列 a と b の各要素を加算し、float 型の配列 c へ格納します。配列の要素数は 8 とします。以降に、一般的な C# で記述したソー

スリストを示します。

リスト2.1●01begin¥01AddCs¥Program.cs
namespace ConsoleApp{

class Program{

static void Main(string[] args){float[] a = { 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f };float[] b = { 11f, 12f, 13f, 14f, 15f, 16f, 17f, 18f };var c = new float[a.Length];for (int i = 0; i < a.Length; i++){}c[i] = a[i] + b[i];for (int i = 0; i < a.Length; i++)
{}

}

}

}

Console.WriteLine("{0} + {1} = {2}", a[i], b[i], c[i]);for 文を使用し、要素分だけ繰り返します。以降に、実行結果を示します。
0 + 10 = 10
1 + 11 = 12
2 + 12 = 14
3 + 13 = 1610