ベクトル演算拡張機能
近年の CPU は、"メディア命令" などと呼ばれる特別なベクトル型とベクトル演算を提供することが多くなっています。 ベクトル型は浮動小数点数や整数の固定長配列で、 ベクトル演算はそれらの要素を同時に演算するもので、 大幅な高速化に貢献します。
普通のD言語のコードで数値データをループ処理していたときに、これらの機能をコンパイラが活用することを、 自動ベクトル化といいます。 自動ベクトル化は、しかしながら、非常に限定された状況でのみ有効で、 ネイティブのベクトル演算の豊富な(そしてしばしば奇妙な) 機能を真に活用することはできていません。
D には配列演算子式という記法があります。例えば:
int[] a,b;
...
a[] += b[];
このコードはコンパイラによってベクトル化されますが、 これも、自動ベクトル化と同様の理由によって効果は限定されています。
ベクトル命令を通常の配列に対して適用することの難しさには、 次のような理由があります:
- ベクトル型には非常に厳密なアラインメントの要求が課せられていて、 通常の配列ではこれを満たせません。
- C 言語の ABI のベクトル演算は、 特殊な名前マングリング規則とcall/return規約と記号デバッグサポートの下にあります。
- ベクトル命令を完全に使いこなす唯一の方法は、インラインアセンブラを使うことです。 しかし、コンパイラがインラインアセンブラのブロックを超えたレジスタ割り当て (やその他の最適化) ができないため、 コードのパフォーマンスは落ちてしまいます。
- 通常の配列処理をベクトル演算を同じデータに対して交互に混ぜると、 知らないうちに非常に悪いパフォーマンスをもたらすことがあります。
特殊なベクトル型を提供することで、これらの問題はなくなります。
core.simd
D言語でベクトル型とベクトル演算を使うには、 core.simd を import します:
import core.simd;
ここで定義される型と演算は、コンパイラの生成しているアーキテクチャの定義するものです。 あるCPUファミリのベクトル型のサポートにばらつきがある場合は、 実行時に利用可能性を検査する必要があるかもしれません。 コンパイラは実行時チェックを生成しません; プログラマが気をつける必要があります。
型名は以下の命名規約にしたがっています:
typeNN
type にはベクトルの要素の型が、NN にはその要素数が入ります。 これらの型名は予約語ではありません。
プロパティ
ベクトル型の持つプロパティは次の通りです:
プロパティ | 説明 |
---|---|
.array | 静的配列としての表現を返す |
静的配列の持つプロパティも全て使うことができます
変換
同じサイズのベクトル型同士は暗黙に変換できます。 ベクトル型は静的配列にキャストすることができます。
整数と浮動小数点数は対応するベクトル型に暗黙変換されます:
int4 v = 7;
v = 3 * v; // v の各要素に 3 を掛ける
ベクトルの要素への個別アクセス
要素に直接アクセスすることはできませんが、o 配列への変換を通すことで可能になります:
int4 v;
(cast(int*)&v)[3] = 2; // 4個のintベクトルの第3要素
cast(int[4])v[3] = 2; // 4個のintベクトルの第3要素
v.array[3] = 2; // 4個のintベクトルの第3要素
v.ptr[3] = 2; // 4個のintベクトルの第3要素
条件コンパイル
ベクトル演算拡張が実装されていれば、 バージョン識別子 D_SIMD がセットされます
特定の型が存在するかどうかは、コンパイル時に IsExpression でチェックできます:
static if (is(typeNN))
... ある ...
else
... ない ...
特定の演算が利用可能かのチェックは、 こうなります:
float4 a,b;
static if (__traits(compiles, a+b))
... 使える ...
else
... 使えない ...
特定のベクトル命令が使えるかの実行時のチェックには、 core.cpuid の関数を使います。
使えない場合の典型的な回避策は、配列ベクトル演算を代わりに使うことです:
float4 a,b;
static if (__traits(compiles, a/b))
c = a / b;
else
c[] = a[] / b[];
X86 と X86_64 でのベクトル演算拡張の実装状況
以下は、X86 と X86_64 アーキテクチャでのベクトル型の対応の説明です。
ベクトル演算は現在のところ OS X での 32bit ターゲットと、 そのほかの 64 bit ターゲットでサポートされています。
core.simd には以下の型が定義されています:
型名 | 説明 | gccでの対応物 |
---|---|---|
void16 | 16 バイトの型無しのデータ | なし |
byte16 | 16 個の byte | signed char __attribute((vector_size(16))) |
ubyte16 | 16 個の ubyte | unsigned char __attribute((vector_size(16))) |
short8 | 8 個の short | short __attribute((vector_size(16))) |
ushort8 | 8 個の ushort | ushort __attribute((vector_size(16))) |
int4 | 4 個の int | int __attribute((vector_size(16))) |
uint4 | 4 個の uint | unsigned __attribute((vector_size(16))) |
long2 | 2 個の long | long __attribute((vector_size(16))) |
ulong2 | 2 個の ulong | unsigned long __attribute((vector_size(16))) |
float4 | 4 個の float | float __attribute((vector_size(16))) |
double2 | 2 個の double | double __attribute((vector_size(16))) |
注: 32 bit gcc では、long ではなく long long です。
演算子 | void16 | byte16 | ubyte16 | short8 | ushort8 | int4 | uint4 | long2 | ulong2 | float4 | double2 |
---|---|---|---|---|---|---|---|---|---|---|---|
= | × | × | × | × | × | × | × | × | × | × | × |
+ += | – | × | × | × | × | × | × | × | × | × | × |
- -= | – | × | × | × | × | × | × | × | × | × | × |
* *= | – | – | – | × | × | – | – | – | – | × | × |
/ /= | – | – | – | – | – | – | – | – | – | × | × |
& &= | – | × | × | × | × | × | × | × | × | – | – |
| |= | – | × | × | × | × | × | × | × | × | – | – |
^ ^= | – | × | × | × | × | × | × | × | × | – | – |
unary~ | – | × | × | × | × | × | × | × | × | – | – |
unary+ | – | × | × | × | × | × | × | × | × | × | × |
unary- | – | × | × | × | × | × | × | × | × | × | × |
リストにない演算子には対応していません。
ベクトル処理用組み込み演算
対応している組み込み演算については core.simd を参照してください