AVXの倍精度実数シャッフル系命令チートシート

# はじめに

AVXで導入されたYMMレジスタは256bitで、64bitで表現される倍精度実数を4つ同時に扱うことができる。で、単にそのまま足したり引いたりするだけなら良いのだが、たまに要素を入れ替えたりする必要が出てくる。この時、二つのレジスタを混ぜたり、レジスタ内の要素を入れ替えたりするシャッフル系の命令が分かりづらかったので、自分用に以下のようなチートシートを作ってみた。

![shuffle.png](https://qiita-image-store.s3.amazonaws.com/0/79744/fc0297ee-7e5f-fac7-c1e3-3150bc6ff96d.png)

SIMDのシャッフルは、わかってしまえば簡単なのだが、最初はインテルのマニュアルの擬似コードみても何がどうなってるかわかりづらいと思う(少なくとも僕はそうだった)。なので、それぞれの命令の動作と使用例を書いてみる。以下では倍精度実数に話を限定する。

# AVXの使い方

## 値の与え方

x86は、AVXから256bitをサポートする。対応するレジスタはYMMで、16本使える。YMMレジスタの下位128bitは、同じ番号のXMMレジスタと共用。AVX命令を使うには、`immintrin.h`をインクルードする。また、YMMレジスタに対応する組み込み型は`__m256d`。`__m256d`の値をプリントする関数を作っておくとデバッグ時に便利。

```cpp
void
print256d(__m256d x){
  printf("%f %f %f %f\n",x[3],x[2],x[1],x[0]);
}
```

YMMレジスタに値を入れる方法は、4つの値を別々にセットする方法と、メモリ上の連続する4つのデータを一度に取ってくる方法の二つがある。まず、4つの値をセットするには`_mm256_set_pd`を使う。

```cpp
  __m256d s1 = _mm256_set_pd(3.0,2.0,1.0,0.0);
  print256d(s1);
  // 3.000000 2.000000 1.000000 0.000000
```

ここで、`_mm256_set_pd`の第四引数が、YMMレジスタの一番下位にセットされることに注意。MSBを左に書く慣習から、左が上位、右が下位になるようにこういう仕様なんだと思う。`_mm256_set_pd`に対応する命令はなく、これをコンパイルするとXMMレジスタの上位と下位にデータをロードし、二つのXMMレジスタをくっつけて一つのYMMレジスタを作るようなコードを吐く(なので遅い)。

メモリ上の連続する4つのデータを取ってくるには`_mm256_load_pd`を使う。このメモリは256bitでアラインされていなければならない。そうでないと実行時にSegmentation Faultが起きる。

```cpp
  __attribute__((aligned(32)))  double x[] = {0.0, 1.0, 2.0, 3.0};
  __m256d s2 = _mm256_load_pd(x);
  print256d(s2);
  // 3.000000 2.000000 1.000000 0.000000
```

上記の例のように配列からロードすると、配列の若いインデックスからYMMの下位に積まれるので、配列の初期化子リストの順番と逆順になることに注意。

## unpacklo/UNPCKLPD

二つのYMMレジスタの、上下128bitのそれぞれ下位64bitをまとめて取ってくる。具体例をみるのが早いと思う。

```cpp
  __m256d src1 = _mm256_set_pd(3.0,2.0,1.0,0.0);
  __m256d src2 = _mm256_set_pd(7.0,6.0,5.0,4.0);
  print256d(_mm256_unpacklo_pd(src1,src2));
  // 6.000000 2.000000 4.000000 0.000000
```

![unpacklo.png](https://qiita-image-store.s3.amazonaws.com/0/79744/efe45b6e-6804-660d-8e1a-107e46c8926a.png)


二つのベクトルsrc1=(3,2,1,0), src2=(7,6,5,4)が与えられた時、src1から(2,0)を、src2から(6,4)を持ってきて、それを混ぜて(6,2,4,0)を作る。

## unpackhi/UNPCKHPD

unpackloの上位64bit版。

```cpp
  __m256d src1 = _mm256_set_pd(3.0,2.0,1.0,0.0);
  __m256d src2 = _mm256_set_pd(7.0,6.0,5.0,4.0);
  print256d(_mm256_unpackhi_pd(src1,src2));
  // 7.000000 3.000000 5.000000 1.000000
```

![unpackhi.png](https://qiita-image-store.s3.amazonaws.com/0/79744/cee75c70-9e23-1c12-abc1-134a67dc9d70.png)

## insertf128/VINSERTF128

XMMレジスタの値をYMMレジスタにコピーする。この時、下位128bitにコピーするか、上位128bitにコピーするかを選べる。第三引数が0なら下位に、1なら上位にコピー。コードは両方やっているが、図は引数が0の時の例。

```cpp
  __m256d src1 = _mm256_set_pd(3.0,2.0,1.0,0.0);
  __m128d src2 = _mm_set_pd(5.0,4.0);
  print256d(_mm256_insertf128_pd(src1,src2,0));
  // 3.000000 2.000000 5.000000 4.000000
  print256d(_mm256_insertf128_pd(src1,src2,1));
  // 5.000000 4.000000 1.000000 0.000000
```

![insertf.png](https://qiita-image-store.s3.amazonaws.com/0/79744/57062983-db55-55ca-4997-b4ed2a4f6f82.png)


## shuffle/VSHUFPD

二つのYMMレジスタを混ぜる。できるレジスタは、(src2,src1,src2,src1)の順番となる。第三引数で、128bitの上位、下位64bitのどちらを取ってくるかを選べる。第三引数は四桁の二進法になっており、0が下位、1が上位を表す。具体例を見るとわかりやすいと思う。以下は第三引数として「0110」、つまり「下位、上位、上位、下位」を選んだ例。

```cpp
  __m256d src1 = _mm256_set_pd(3.0,2.0,1.0,0.0);
  __m256d src2 = _mm256_set_pd(7.0,6.0,5.0,4.0);
  const int imm = 0*8 + 1*4+ 1*2 + 0*1;
  print256d(_mm256_shuffle_pd(src1,src2,imm));
  // 6.000000 3.000000 5.000000 0.000000
```
![shuffle.png](https://qiita-image-store.s3.amazonaws.com/0/79744/cbf1992b-ec8f-df7b-6caa-811f5f38b841.png)

## blend/BLENDPD

二つのYMMレジスタを混ぜる。これはdestレジスタの、対応する場所を、src1とsrc2のどちらを取ってくるかを選ぶ。第三引数は四桁の二進法になっており、0がsrc1を、1がsrc2を表す。以下は、第三引数として「0110」を指定した場合の例。

```cpp
  __m256d src1 = _mm256_set_pd(3.0,2.0,1.0,0.0);
  __m256d src2 = _mm256_set_pd(7.0,6.0,5.0,4.0);
  const int imm = 0*8 + 1*4 + 1*2 + 0*1; // 0110
  print256d(_mm256_blend_pd(src1,src2,imm));
  // 3.000000 6.000000 5.000000 0.000000
```

![blend.png](https://qiita-image-store.s3.amazonaws.com/0/79744/12cd4fe3-53f5-f11b-75d3-6be3136e68b3.png)

## permute/VPERMPD

一つのYMMレジスタの値を任意に並び替える(AVX2)。この時、おなじ要素を重複して選んで、複数コピーすることもできる。だからもともとのベクトルが(3,2,1,0)だった場合、(0,0,0,0)から(3,3,3,3)まで自由に作ることができる。第二引数は四桁の四進数になっており、それぞれどこを取ってくるか指定する。以下は、第三引数として「1323」を指定した場合の例。

```cpp
  __m256d src = _mm256_set_pd(3.0,2.0,1.0,0.0);
  const int imm = 1*64 + 3*16 + 2*4 + 3*1; // 1323
  print256d(_mm256_permute4x64_pd(src,imm));
  // 1.000000 3.000000 2.000000 3.000000
```

![permute.png](https://qiita-image-store.s3.amazonaws.com/0/79744/40aeb28c-8601-c33b-e01f-cc5959adefe4.png)


# まとめ

いつも「あれ？どっちが上位だっけ？」とか、「混ぜる奴の第三引数ってどういう意味だっけ？」とかごちゃごちゃになるのでまとめてみた。