AVX/AVX2による256ビットロード命令

# はじめに
256ビットのロード命令について説明します．
｜でまとめている場合，すべて同じパフォーマンスです．

LDDQUを，今の環境で使うと素晴らしくなるパターンが他にもあれば教えてください．

## _mm256_load|loadu_ps|pd|si256 (AVX)
```cpp
__m256 _mm256_load_ps (float const * mem_addr)
__m256 _mm256_loadu_ps (float const * mem_addr)
__m256d _mm256_load_pd (double const * mem_addr)
__m256d _mm256_loadu_pd (double const * mem_addr)
__m256d _mm256_load_si256 (double const * mem_addr)
__m256i _mm256_loadu_si256 (__m256i const * mem_addr)
asm: vmovaps ymm, m256/ymm //load_ps
asm: vmovups ymm, m256/ymm //loadu_ps
asm: vmovapd ymm, m256 //load_pd
asm: vmovups ymm, m256 //loadu_pd
asm: vmovdqa ymm, m256 //load_si256
asm: vmovdqu ymm, m256 //loadu_si256
```

| Architecture | Latency | Throughput |Uops|
|--------------|:-------:|:----------:|:--:|
| Alderlake    |mem. dep.|    0.33    |  - |
| Icelake      |   5/8   |    0.5     |  1 |
| Skylake      |   5/8   |    0.5     |  1 |
| Broadwell    |   6/7   |    0.5     |  1 |
| Haswell      |   6/7   |    0.5     |  1 |
| Ivy Bridge   |   6/8   |     1      |  1 |
| Sandy Bridge |   6/8   |     1      |  1 |
| Zen3         |   8/9   |    0.5     |  1 |
| Zen2         |    8    |    0.5     |  1 |
| Zen          |    8    |     1      |  2 |

* メモリからのロードの場合のレイテンシです．
* レジスタ・レジスタ間のvmovは，多くの場合で0コストです．
* Alderlakeに関しては，AIDA64からの情報しかなく，レイテンシはメモリ依存としか記載できません．
* 8/9などのレイテンシの少ないほうはメモリからのロード，多いほうはアドレス計算を含むロードです．


AIDA64より，アライメントがずれた場合のパフォーマンスは以下の通りです．

```asm
VMOVUPS ymm, [m256 + 4]
```

| Architecture | Latency | Throughput |Uops|
|--------------|:-------:|:----------:|:--:|
| Alderlake    |mem. dep.|    0.78    |  - |
| Icelake      |mem. dep.|    1.92    |  - |
| Skylake      |mem. dep.|    2.25    |  - |
| Broadwell    |mem. dep.|    2.25    |  - |
| Haswell      |mem. dep.|    2.25    |  - |
| Ivy Bridge   |mem. dep.|    5.83    |  - |
| Sandy Bridge |mem. dep.|    6.83    |  - |
| Zen3         |mem. dep.|    1.58    |  - |
| Zen2         |mem. dep.|    1.08    |  - |
| Zen          |mem. dep.|    1.5     |  - |


 
**説明**  
浮動小数点や整数を256ビットロードする命令です．
`float`のpsならの8要素を，`double`のpdなら4要素を読み込みます．
si256なら整数ロードであり，`char`なら32要素，`short`なら16要素，`int`なら8要素，`long long`なら4要素読み込みます．
uとaでメモリのアライメントがそろっているか否かを表す命令となります．

ロードのレイテンシは，本質的には，どのメモリからか，L1～3キャッシュからのコピーなのかで変わります．
このパフォーマンスの数値は，数値的にL1キャッシュからのロードになっています．

SSEの場合と違って，アライメントがそろっていないに状態にもかかわらず，そろっているつもりの命令を出してもパフォーマンスが低下するだけでプログラムは落ちません．
言い換えれば，下記SSE命令は落ちますが，AVX命令は落ちません．

```c++
movdqa xmm m128 //SSE
vmovdqa xmm m128 //AVXの128ビット用命令
```


## _mm256_stream_load_si256 (AVX)
```cpp
__m256i _mm256_stream_load_si256 (__m256i const* mem_addr)
asm: vmovntdqa ymm, m256
```

| Architecture | Latency | Throughput |Uops|
|--------------|:-------:|:----------:|:--:|
| Alderlake    |mem. dep.|    0.5     |  - |
| Icelake      |   5/8   |    0.5     |  2 |
| Skylake      |   5/8   |    0.5     |  2 |
| Broadwell    |   6/7   |    0.5     |  1 |
| Haswell      |   6/7   |    0.5     |  1 |
| Zen3         |   8/9   |    0.5     |  1 |
| Zen2         |    8    |    0.5     |  1 |
| Zen          |    8    |     1      |  2 |

* Icelake SkylakeのUopsが多いです（Fog先生のサイトも同じです）．
* スループットはAlderlakeで速くなっていません（要検証）．

**説明**  
キャッシュを介さず整数を256ビットロードする命令です．
この命令は，アライメントがそろっていないといけません．

なお，SSEの場合と同様に，浮動小数点に対するstream_load_ps|pd命令はありません．
必要な場合は，下記のようにポインタをキャストして使用してください．

```cpp
inline __m256 _mm256_stream_load_ps(float* src)
{	
	return _mm256_castsi256_ps(_mm256_stream_load_si256((__m256i*)src));
}

inline __m256d _mm256_stream_load_pd(double* src)
{
	return _mm256_castsi256_pd(_mm256_stream_load_si256((__m256i*)src));
}
```

## _mm256_lddqu_si256 (AVX2)
```cpp
__m256i _mm256_lddqu_si256 (__m256i const * mem_addr)
asm: vlddqu ymm, m256
```

| Architecture | Latency | Throughput |Uops|
|--------------|:-------:|:----------:|:--:|
| Alderlake    |mem. dep.|    0.33    |  - |
| Icelake      |   5/8   |    0.5     |  1 |
| Skylake      |   5/8   |    0.5     |  1 |
| Broadwell    |   6/7   |    0.5     |  1 |
| Haswell      |   6/7   |    0.5     |  1 |
| Ivy Bridge   |   6/8   |     1      |  1 |
| Sandy Bridge |   6/8   |     1      |  1 |
| Zen3         |   8/9   |    0.5     |  1 |
| Zen2         |    8    |    0.5     |  1 |
| Zen          |    8    |     1      |  2 |

* uopsによると，load命令とすべて同じですが，最善の場合です．

以下は，メモリ境界をまたぐ場合の例です（AIDA64によるデータ）．

```asm
VLDDQU ymm, [m256 + 4]
VMOVUPS ymm, [m256 + 4]
```

| Architecture | Latency | Throughput |Uops|
|--------------|:-------:|:----------:|:--:|
| Alderlake    |mem. dep.|    0.78    |  - |
| Icelake      |mem. dep.|    1.92    |  - |
| Skylake      |mem. dep.|    2.25    |  - |
| Broadwell    |mem. dep.|    2.25    |  - |
| Haswell      |mem. dep.|    2.25    |  - |
| Ivy Bridge   |mem. dep.|    5.83    |  - |
| Sandy Bridge |mem. dep.|    6.83    |  - |
| Zen3         |mem. dep.| 1.58/2.25  |  - |
| Zen2         |mem. dep.|  1.08/1.0  |  - |
| Zen          |mem. dep.|    1.5     |  - |
 
* Zen2, Zen3のみ，VLDDQU,VMOVUPSのパフォーマンスが違います．（vmovups/vlddqu）
 
**説明**  
アライメントのそろっていない整数を256ビットロードする命令です．
レイテンシとスループットは，通常のloadと変わりません．

インテルのマニュアルによると概ね下記のように記述されています．
> この命令は，キャッシュライン境界をまたぐ場合，通常のloadu命令よりも速い場合があります．
loadu_si256(VLDDQU)でロードする必要があるデータについて，それを変更して同じ場所に保存する必要がある場合は，loaduを使用してください．キャッシュラインサイズは，通常64バイト（512ビット）です．
https://www.felixcloutier.com/x86/lddqu

AMDのほうを引くと下記になっています．
>Loads unaligned double quadwords from a memory location to a destination register.
Like the (V)MOVUPD instructions, (V)LDDQU loads a 128-bit or 256-bit operand from an unaligned memory location. 
However, to improve performance when the memory operand is actually misaligned, (V)LDDQU may read an aligned 16 or 32 bytes to get the first part of the operand, and an aligned 16 or 32 bytes to get the second part of the operand. This behavior is implementation-specific, and (V)LDDQU may only read the exact 16 or 32 bytes needed for the memory operand. If the memory operand is in a memory range where reading extra bytes can cause performance or functional issues, use (V)MOVUPD instead of (V)LDDQU.
Memory operands that are not aligned on 16-byte or 32-byte boundaries do not cause general-protection exceptions.
There are legacy and extended forms of the instruction:

簡単に説明すると，LDDQUはアラインされた2つのメモリを呼んでレジスタを合成します．一方でloadu命令（movups等）は，正しくそのメモリアドレスを読みだします．
レジスタの最終的な状態は一緒ですが，メモリアクセスのパターンが違うことを意味しています．
多くの場合，余計なロードをしないこの命令よりもloadu命令のほうが速く動作するコードになります．
特に，ロードとストアのアドレスが一致する場合などでは，lddqu命令は必要ありません．
経験上，データを1つづつずらしながら大量に読み込むような処理（畳み込みなど）を多くの要素でループアンロールする場合，lddqu命令のほうが速くなります．

なお，SSEの場合と同様に，浮動小数点に対するlddqu_ps|pd命令はありません．
必要な場合は，下記のようにポインタをキャストして使用してください．

```cpp
inline __m256 _mm256_lddqu_ps(float* src)
{
	return _mm256_castsi256_ps(_mm256_lddqu_si256((__m256i*)src));	
}

inline __m256d _mm256_lddqu_pd(double* src)
{
	return _mm256_castsi256_pd(_mm256_lddqu_si256((__m256i*)src));
}
```