はじめに
256ビットのロード命令について説明します.
|でまとめている場合,すべて同じパフォーマンスです.
LDDQUを,今の環境で使うと素晴らしくなるパターンが他にもあれば教えてください.
_mm256_load|loadu_ps|pd|si256 (AVX)
__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より,アライメントがずれた場合のパフォーマンスは以下の通りです.
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命令は落ちません.
movdqa xmm m128 //SSE
vmovdqa xmm m128 //AVXの128ビット用命令
_mm256_stream_load_si256 (AVX)
__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命令はありません.
必要な場合は,下記のようにポインタをキャストして使用してください.
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)
__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によるデータ).
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命令はありません.
必要な場合は,下記のようにポインタをキャストして使用してください.
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));
}