From IA-32 to AVX-512
システムプログラミング会
2016/7/2 光成滋生
• IA-32からAVX-512まで主にレジスタサイズの観点から
ゆるゆると観察する小ネタ集
• SIMD成分は少なめ m(_ _)m
概要
2/30
• Intel初の32bit CPU
• 8個の32bit汎用レジスタ eax, ebx, ecx, edx, esi, edi, ebp, esp
• もともと8個の16-bit汎用レジスタを拡張したもの
• eaxの下位16bitがax
• axの下位8bitがal
• レジスタ間はmovで移動
• これが全ての"始まり"かもしれない
i386(Intel 386)
eax
ax
alah
; eax = 0x99887766のとき
mov al, 12h ; eax = 0x99887712, ax = 0x7712
mov ah, 34h ; eax = 0x99883412, ax = 0x3412
3/30
• 命令のフォーマットの一つ
• 8個のレジスタなので3bit
• src(ソース)とdst(デスティネーション)それぞれ必要
• レジスタ間のときはmod = 0b11
ModRM
7 6 5 4 3 2 1 0
+------+------+------+------+------+------+------+------+
| mod | src | reg |
+------+------+------+------+------+------+------+------+
4/30
• 参考文献
• Intel 64 and IA-32 Architectures Software Developer’s Manual
• mov eax, ecxならeax = 0, ecx = 1なので
ModRM = 0b11 001 000 = 0xc8だから8b c8
movの場合
89 /r MOV r/m32,r32 MR ; Move r32 to r/m32
>cat test.asm
mov eax, ecx
>nasm -l a.lst -f win32 test.asm
>cat a.lst
1 00000000 89C8 mov eax, ecx
5/30
• 486DX
• FPU内蔵タイプ
• 8個の80bit浮動小数点数レジスタ st0, st1, ..., st7
• Pentium with MMX
• 8個の64bit SIMDレジスタ mm0, ...., mm7
• 整数演算演算のみサポート
• movd mm0, eaxなど
• K6-2の3D Now!
• AMDがMMXレジスタで浮動小数点数を扱えるように拡張
機能追加
6/30
• 8個の128-bit SIMDレジスタ
• SSE<=>MMX間はmovdq2q, movq2dqなど
• OSの対応が必要
• SSEレジスタの退避、復元が必要
• MMXレジスタはOSの対応は不要だった
• なんで?
• FPUと共用
• 代わりにemms, femmsが必要
• MMXで壊した状態でFPUを使う前にリセット
SSE
7/30
• elfバイナリに文字列を埋め込んでみる
• https://github.com/herumi/misc/blob/master/emb/
• 構想20年 実装3時間w
• 32bitバイナリa.outを用意する(手抜きで64bit不可)
• a.outにhelloという文字列を埋め込んでembファイルを作る
• もちろんembはa.outと同じ動きをする
• embには"hello"が埋め込まれている
小ネタ
>python embed-str.py a.out -o emb -s hello
>python embed-str.py emb
hello
>python embed-str.py a.out
// 何も表示されない
8/30
• 名前しか違わない
• どこに埋め込んだ?
全セクションを逆アセしてみる
>objdump -D --no-show-raw-insn ./a.out > a.asm
>objdump -D --no-show-raw-insn ./emb > b.asm
>diff a.asm b.asm
2c2
< ./a.out: file format elf32i386
---
> ./emb: file format elf32i386
./a.out: file format elf32i386
Disassembly of section .interp:
08048154 <.interp>:
8048154: das
8048155: insb (%dx),%es:(%edi)
8048156: imul
$0x6c2d646c,0x2f(%edx),%esp
804815d: imul
$0x6f732e78,0x75(%esi),%ebp
8048164: xor %cs:(%eax),%al
Disassembly of section .note.ABI-tag:
08048168 <.note.ABI-tag>:
8048168: add $0x0,%al
804816a: add %al,(%eax)
804816c: adc %al,(%eax)
...
9/30
• movのフォーマット
• add, adc, and, xor, or, sbb, sub, cmp, movなど
• @shinhさんコメント
• http://www1.cs.columbia.edu/~angelos/Papers/hydan.pdf
オペランドによって複数の種類がる
89 /r MOV r/m32,r32 MR ; Move r32 to r/m32
8B /r MOV r32,r/m32 RM ; Move r/m32 to r32
89C8 mov eax, ecx
8BC1 mov eax, ecx
10/30
• axを操作したあとeaxを使うとペナルティ
• 依存関係を切る
• Pentum Pro以降のパイプラインの深くなったCPUで発生
• 最近はそれほど気にしなくてもよいかも
• movzxで0拡張するかして幅を揃えるようにする
パーシャルレジスタストール
mov eax, [esi]
mov al, 3
mov ecx, eax ; ペナルティ
11/30
• 16個の64bit汎用レジスタ(r8, ..., r15)
• レジスタを特定するには4bit必要
• ModRMでは1bit足りない
• rexプレフィクス登場
x64
7 6 5 4 3 2 1 0
+------+------+------+------+------+------+------+------+
| 0 1 0 0 | REX.w| REX.r| REX.x| REX.b|
+------+------+------+------+------+------+------+------+
mov <b>, <r> ; b = [b3:b2:b1:b0], r = [r3:r2:r1:r0]
| 0 1 0 0 | 1 !r3 0 !b3 | 0x89 | 1 1 b2 b1 b0 r2 r1 r0|
12/30
• 64bit環境で32bitレジスタを操作すると
上位32bitはクリアされる
• mov ax, 0x1234のときのeaxと挙動が違う
• 多分依存関係を切るため
32bitとの違い
; rax = 0x1234567812345678
mov eax, 0x11223344
; rax = 0x0000000011223344
13/30
• 何もしない命令(0x90)
• 32bit時代はxchg eax, eaxのエリアス
• 64bit自体はeaxの上位がクリアされてしまう!
• NOP = 0x90と定義
• mov eax, eaxも同様
• movzx, eax, al(ゼロ拡張と同じ動き)
• コンパイラの生成コードを見て
無駄なことしてるなと勘違いしないように
NOP
14/30
• 16個の256bit SIMDレジスタ(ymm0, ..., ymm15)
• 下位128bitは xmmレジスタでアクセス
• 3オペランドの命令体系
• addpd ymm0, ymm1, ymm2
• VEXプレフィクス
• レジスタ指定に4bit x 3 + 1(xmmかymmか) = 13bit必要
• かなりややこしい
AVX, AVX2
3byte VEX
|7 6 5 4 3 2 1 0| |7 6 5 4 3 2 1 0| <code> ModRM...
0xC4 |R|X|B|m-m m m m| |W|v v v v|L|p p|
15/30
• SSEの命令はymmレジスタの上位をクリアしない
レガシーSSEとAVX
addpd xmm0, xmm1 ; ymm0の上位は変化せず
vaddpd xmm0, xmm0, xmm1 ; ymm0の上位はクリア
16/30
• fmath(https://github.com/herumi/fmath)
• 高速なexpの近似計算
• http://www.slideshare.net/herumi/exp-9499790
• 性能劣化
• VS2012からVS2013にしたときに標準関数が遅くなる場面
• もちろん普通に使うと速い
悩んだ問題
VS2012 VS2013
std::exp 62.2 349.8
fmath::exp 33.8 33.0
17/30
• ループ回数が7だと遅くならない
• mainでaの値は使われない
最小限のコード(当時)
const struct A {
float a[8];
A() {
const float x = log(2.0f);
for (int i = 0; i < 8; i++) a[i] = x;
}
} a;
int main() {
ここでstd::expを関数ポインタ経由で呼び出すと遅くなる
}
18/30
• 発現条件
• VC2013は8回のループを展開し
vinsertf128というAVXの1命令に置き換えた
• std::expはレガシーなSSEで書かれていた
• 理由
• ymmレジスタの上位が0クリアでないと認識されて
ペナルティを食らっていた
• 解決方法
• コンパイラのバグ(もちろん修正済み)
• AVXを使った後SSEを使う可能性があるときはvzeroupperで
ゼロクリアしなければならない
• ただ混在しないようにするのが理想
• 遅くなる関数自体には問題がないので分かりにくい
何が起こっていたのか
19/30
• Intel最適化マニュアル11章
• Mixing AVX Code with SSE Code
CPUは状態を持っている
20/30
• Intelのエミュレータ
• https://software.intel.com/en-us/articles/pre-release-license-
agreement-for-intel-software-development-emulator-accept-
end-user-license-agreement-and-download
検出ツール
vzeroupperなし
sde -ast -- nonzero-upper-penalty.exe
# AVX_to_SSE_transition_instances: 102
# SSE_to_AVX_transition_instances: 102
vzeroupperあり
sde -ast -- nonzero-upper-penalty.exe 1
# AVX_to_SSE_transition_instances: 0
# SSE_to_AVX_transition_instances: 0
21/30
• https://github.com/herumi/opti/blob/master/nonzero-upper-
penalty.cpp
• vzeroupperを呼ばないと1.8倍ぐらい遅くなる
サンプル
struct Code : public Xbyak::CodeGenerator {
explicit Code(bool clear)
{
vxorpd(ym0, ym0);
if (clear) vzeroupper();
mov(eax, N);
L("@@");
addpd(xm0, xm0);
sub(eax, 1);
jnz("@b");
ret();
}
};
22/30
• Intel 64 and IA-32 Architectures Software Developer's
Manual, Volume 3 System Programming Guide
• 19章Performance-Monitoring Events
Linuxのperf
Event Umask mnemonic 説明
C1h 08h OTHER_ASSISTS.AVX_TO_SSE AVX-256からSSEへの切り替え回数
C1h 10h OTHER_ASSISTS.SSE_TO_AVX SSEからAVX-256への切り替え回数
perf stat -e r08c1 -e r10c1 ./nonzero-upper-penalty
Performance counter stats for './ nonzero-upper-penalty ':
10000001 r08c1
9999978 r10c1
23/30
• 32個の512bit SIMDレジスタ(zmm0, ..., zmm31)
• 5bit x 3 + 2(XかYかZか)= 17bit必要
• VEXでは足りないのでEVEX
• 7個の64bitマスクレジスタ(k0, ..., k7)
• 演算結果の一部をゼロにしたりできる
• 指定が自由にできる
• マスクレジスタ自体多少の演算ができる
• kaddw k1, k2, k3 ; kshiftlq k1, k2, 5 ; etc.
• 命令ごとに丸め方法を指定できる
• nearest, toward -inf, toward +inf, round to zero
• AVX-512は○オペランド?
AVX-512
24/30
• 3オペランド?
• 丸め方法の指定
• マスクレジスタの指定
• ゼロで埋めるか
例
vaddpd zmm10, zmm20, zmm30
vaddpd zmm10, zmm20, zmm30, {rd-sae}
vaddpd zmm10{k2}, zmm20, zmm30, {rd-sae}
vaddpd zmm10{k2}{z}, zmm20, zmm30, {rd-sae}
25/30
• とてもややこしい
EVEX
| EVEX |
|0x62 P0 P1 P2| opcode ModRM/M [SIB] [Disp] [Imm]
| 7 6 5 4 3 2 1 0 |
P0 | R | X | B | R'| 0 | 0 | m | m |
P1 | W | v | v | v | v | 1 | p | p |
P2 | z | L'| L | b | V'| a | a | a |
vaddpd [a4 a3 a2 a1 a0],[b4 b3 b2 b1 b0],[c4 c3 c2 c1 c0]
0x62|!a3 !c4 !c3 !a4 0001|1 b3 b2 b1 b0 101|0100 !b4 aaa|
<code> |11 a2 a1 a0 c2 c1 c0| ...
26/30
• zmm0=[8:7:6:5:4:3:2:1], [rax] = [2:1]のときどうなるか
ゼロクリアクイズ(問題)
命令 意味 結果
movsd xmm0, xmm0 最下位doubleを移動
movsd xmm0, [rax] 最下位doubleを移動
vmovsd xmm0, xmm0, xmm0 最下位doubleを移動
vmovsd xmm0, [rax] 最下位doubleを移動
addsd xmm0, xmm0 最下位doubleを加算
addpd xmm0, xmm0 double x 2を加算
vaddsd xmm0, xmm0, xmm0 最下位doubleを加算
vaddpd xmm0, xmm0, xmm0 double x 2を加算
vaddpd ymm0, ymm0, ymm0 double x 4を加算
27/30
命令 意味 効果
movsd xmm0, xmm0 最下位doubleを移動 [8:7:6:5:4:3:2:1]
movsd xmm0, [rax] 最下位doubleを移動 [8:7:6:5:4:3:0:1]
vmovsd xmm0, xmm0, xmm0 最下位doubleを移動 [0:0:0:0:0:0:2:1]
vmovsd xmm0, [rax] 最下位doubleを移動 [0:0:0:0:0:0:0:1]
addsd xmm0, xmm0 最下位doubleを加算 [8:7:6:5:4:3:2:2]
addpd xmm0, xmm0 double x 2を加算 [8:7:6:5:4:3:4:2]
vaddsd xmm0, xmm0, xmm0 最下位doubleを加算 [0:0:0:0:0:0:2:2]
vaddpd xmm0, xmm0, xmm0 double x 2を加算 [0:0:0:0:0:0:4:2]
vaddpd ymm0, ymm0, ymm0 double x 4を加算 [0:0:0:0:8:6:4:2]
ゼロクリアクイズ(答え)
• zmm0=[8:7:6:5:4:3:2:1], [rax] = [2:1]のときどうなるか
• https://github.com/herumi/opti/tree/master/upper-zero
28/30
• 512bit x 32 = 2048byte = 2KiB!
• コンテキストスイッチの度に全部保存?
• xsaveopt, xsavec
• Vol. 1 13章「Managing State Using the XSAVE Feature Set」
• メモリコピーを減らす仕組み
• 各レジスタが変更されたか否かのビット情報を保持
• その情報にしたがって必要なものだけ退避する命令
レジスタの退避・復元
29/30
• SIMDはテーブル引きに弱い
• Haswellで登場(しかし使わない方が速いぐらいだった)
• Skylakeでかなり改善された
• AVX-512ではマスクレジスタが登場して大分使いやすくなっ
た(ようだ)
(おまけ)高速化されたgather命令
int tbl[16];
double a[16], b[16];
for (int i = 0; i < 16; i++) {
b[i] = a[tbl[i]];
}
vmovdqu zmm0, [rsp + tbl]
kxnor k1, k0, k0 ; 全部1をたてる(c ← !(a ^ b))
vpgatherdd zmm2{k1}, [rax + zmm0 * 4]
30/30

From IA-32 to avx-512

  • 1.
    From IA-32 toAVX-512 システムプログラミング会 2016/7/2 光成滋生
  • 2.
  • 3.
    • Intel初の32bit CPU •8個の32bit汎用レジスタ eax, ebx, ecx, edx, esi, edi, ebp, esp • もともと8個の16-bit汎用レジスタを拡張したもの • eaxの下位16bitがax • axの下位8bitがal • レジスタ間はmovで移動 • これが全ての"始まり"かもしれない i386(Intel 386) eax ax alah ; eax = 0x99887766のとき mov al, 12h ; eax = 0x99887712, ax = 0x7712 mov ah, 34h ; eax = 0x99883412, ax = 0x3412 3/30
  • 4.
    • 命令のフォーマットの一つ • 8個のレジスタなので3bit •src(ソース)とdst(デスティネーション)それぞれ必要 • レジスタ間のときはmod = 0b11 ModRM 7 6 5 4 3 2 1 0 +------+------+------+------+------+------+------+------+ | mod | src | reg | +------+------+------+------+------+------+------+------+ 4/30
  • 5.
    • 参考文献 • Intel64 and IA-32 Architectures Software Developer’s Manual • mov eax, ecxならeax = 0, ecx = 1なので ModRM = 0b11 001 000 = 0xc8だから8b c8 movの場合 89 /r MOV r/m32,r32 MR ; Move r32 to r/m32 >cat test.asm mov eax, ecx >nasm -l a.lst -f win32 test.asm >cat a.lst 1 00000000 89C8 mov eax, ecx 5/30
  • 6.
    • 486DX • FPU内蔵タイプ •8個の80bit浮動小数点数レジスタ st0, st1, ..., st7 • Pentium with MMX • 8個の64bit SIMDレジスタ mm0, ...., mm7 • 整数演算演算のみサポート • movd mm0, eaxなど • K6-2の3D Now! • AMDがMMXレジスタで浮動小数点数を扱えるように拡張 機能追加 6/30
  • 7.
    • 8個の128-bit SIMDレジスタ •SSE<=>MMX間はmovdq2q, movq2dqなど • OSの対応が必要 • SSEレジスタの退避、復元が必要 • MMXレジスタはOSの対応は不要だった • なんで? • FPUと共用 • 代わりにemms, femmsが必要 • MMXで壊した状態でFPUを使う前にリセット SSE 7/30
  • 8.
    • elfバイナリに文字列を埋め込んでみる • https://github.com/herumi/misc/blob/master/emb/ •構想20年 実装3時間w • 32bitバイナリa.outを用意する(手抜きで64bit不可) • a.outにhelloという文字列を埋め込んでembファイルを作る • もちろんembはa.outと同じ動きをする • embには"hello"が埋め込まれている 小ネタ >python embed-str.py a.out -o emb -s hello >python embed-str.py emb hello >python embed-str.py a.out // 何も表示されない 8/30
  • 9.
    • 名前しか違わない • どこに埋め込んだ? 全セクションを逆アセしてみる >objdump-D --no-show-raw-insn ./a.out > a.asm >objdump -D --no-show-raw-insn ./emb > b.asm >diff a.asm b.asm 2c2 < ./a.out: file format elf32i386 --- > ./emb: file format elf32i386 ./a.out: file format elf32i386 Disassembly of section .interp: 08048154 <.interp>: 8048154: das 8048155: insb (%dx),%es:(%edi) 8048156: imul $0x6c2d646c,0x2f(%edx),%esp 804815d: imul $0x6f732e78,0x75(%esi),%ebp 8048164: xor %cs:(%eax),%al Disassembly of section .note.ABI-tag: 08048168 <.note.ABI-tag>: 8048168: add $0x0,%al 804816a: add %al,(%eax) 804816c: adc %al,(%eax) ... 9/30
  • 10.
    • movのフォーマット • add,adc, and, xor, or, sbb, sub, cmp, movなど • @shinhさんコメント • http://www1.cs.columbia.edu/~angelos/Papers/hydan.pdf オペランドによって複数の種類がる 89 /r MOV r/m32,r32 MR ; Move r32 to r/m32 8B /r MOV r32,r/m32 RM ; Move r/m32 to r32 89C8 mov eax, ecx 8BC1 mov eax, ecx 10/30
  • 11.
    • axを操作したあとeaxを使うとペナルティ • 依存関係を切る •Pentum Pro以降のパイプラインの深くなったCPUで発生 • 最近はそれほど気にしなくてもよいかも • movzxで0拡張するかして幅を揃えるようにする パーシャルレジスタストール mov eax, [esi] mov al, 3 mov ecx, eax ; ペナルティ 11/30
  • 12.
    • 16個の64bit汎用レジスタ(r8, ...,r15) • レジスタを特定するには4bit必要 • ModRMでは1bit足りない • rexプレフィクス登場 x64 7 6 5 4 3 2 1 0 +------+------+------+------+------+------+------+------+ | 0 1 0 0 | REX.w| REX.r| REX.x| REX.b| +------+------+------+------+------+------+------+------+ mov <b>, <r> ; b = [b3:b2:b1:b0], r = [r3:r2:r1:r0] | 0 1 0 0 | 1 !r3 0 !b3 | 0x89 | 1 1 b2 b1 b0 r2 r1 r0| 12/30
  • 13.
    • 64bit環境で32bitレジスタを操作すると 上位32bitはクリアされる • movax, 0x1234のときのeaxと挙動が違う • 多分依存関係を切るため 32bitとの違い ; rax = 0x1234567812345678 mov eax, 0x11223344 ; rax = 0x0000000011223344 13/30
  • 14.
    • 何もしない命令(0x90) • 32bit時代はxchgeax, eaxのエリアス • 64bit自体はeaxの上位がクリアされてしまう! • NOP = 0x90と定義 • mov eax, eaxも同様 • movzx, eax, al(ゼロ拡張と同じ動き) • コンパイラの生成コードを見て 無駄なことしてるなと勘違いしないように NOP 14/30
  • 15.
    • 16個の256bit SIMDレジスタ(ymm0,..., ymm15) • 下位128bitは xmmレジスタでアクセス • 3オペランドの命令体系 • addpd ymm0, ymm1, ymm2 • VEXプレフィクス • レジスタ指定に4bit x 3 + 1(xmmかymmか) = 13bit必要 • かなりややこしい AVX, AVX2 3byte VEX |7 6 5 4 3 2 1 0| |7 6 5 4 3 2 1 0| <code> ModRM... 0xC4 |R|X|B|m-m m m m| |W|v v v v|L|p p| 15/30
  • 16.
    • SSEの命令はymmレジスタの上位をクリアしない レガシーSSEとAVX addpd xmm0,xmm1 ; ymm0の上位は変化せず vaddpd xmm0, xmm0, xmm1 ; ymm0の上位はクリア 16/30
  • 17.
    • fmath(https://github.com/herumi/fmath) • 高速なexpの近似計算 •http://www.slideshare.net/herumi/exp-9499790 • 性能劣化 • VS2012からVS2013にしたときに標準関数が遅くなる場面 • もちろん普通に使うと速い 悩んだ問題 VS2012 VS2013 std::exp 62.2 349.8 fmath::exp 33.8 33.0 17/30
  • 18.
    • ループ回数が7だと遅くならない • mainでaの値は使われない 最小限のコード(当時) conststruct A { float a[8]; A() { const float x = log(2.0f); for (int i = 0; i < 8; i++) a[i] = x; } } a; int main() { ここでstd::expを関数ポインタ経由で呼び出すと遅くなる } 18/30
  • 19.
    • 発現条件 • VC2013は8回のループを展開し vinsertf128というAVXの1命令に置き換えた •std::expはレガシーなSSEで書かれていた • 理由 • ymmレジスタの上位が0クリアでないと認識されて ペナルティを食らっていた • 解決方法 • コンパイラのバグ(もちろん修正済み) • AVXを使った後SSEを使う可能性があるときはvzeroupperで ゼロクリアしなければならない • ただ混在しないようにするのが理想 • 遅くなる関数自体には問題がないので分かりにくい 何が起こっていたのか 19/30
  • 20.
    • Intel最適化マニュアル11章 • MixingAVX Code with SSE Code CPUは状態を持っている 20/30
  • 21.
    • Intelのエミュレータ • https://software.intel.com/en-us/articles/pre-release-license- agreement-for-intel-software-development-emulator-accept- end-user-license-agreement-and-download 検出ツール vzeroupperなし sde-ast -- nonzero-upper-penalty.exe # AVX_to_SSE_transition_instances: 102 # SSE_to_AVX_transition_instances: 102 vzeroupperあり sde -ast -- nonzero-upper-penalty.exe 1 # AVX_to_SSE_transition_instances: 0 # SSE_to_AVX_transition_instances: 0 21/30
  • 22.
    • https://github.com/herumi/opti/blob/master/nonzero-upper- penalty.cpp • vzeroupperを呼ばないと1.8倍ぐらい遅くなる サンプル structCode : public Xbyak::CodeGenerator { explicit Code(bool clear) { vxorpd(ym0, ym0); if (clear) vzeroupper(); mov(eax, N); L("@@"); addpd(xm0, xm0); sub(eax, 1); jnz("@b"); ret(); } }; 22/30
  • 23.
    • Intel 64and IA-32 Architectures Software Developer's Manual, Volume 3 System Programming Guide • 19章Performance-Monitoring Events Linuxのperf Event Umask mnemonic 説明 C1h 08h OTHER_ASSISTS.AVX_TO_SSE AVX-256からSSEへの切り替え回数 C1h 10h OTHER_ASSISTS.SSE_TO_AVX SSEからAVX-256への切り替え回数 perf stat -e r08c1 -e r10c1 ./nonzero-upper-penalty Performance counter stats for './ nonzero-upper-penalty ': 10000001 r08c1 9999978 r10c1 23/30
  • 24.
    • 32個の512bit SIMDレジスタ(zmm0,..., zmm31) • 5bit x 3 + 2(XかYかZか)= 17bit必要 • VEXでは足りないのでEVEX • 7個の64bitマスクレジスタ(k0, ..., k7) • 演算結果の一部をゼロにしたりできる • 指定が自由にできる • マスクレジスタ自体多少の演算ができる • kaddw k1, k2, k3 ; kshiftlq k1, k2, 5 ; etc. • 命令ごとに丸め方法を指定できる • nearest, toward -inf, toward +inf, round to zero • AVX-512は○オペランド? AVX-512 24/30
  • 25.
    • 3オペランド? • 丸め方法の指定 •マスクレジスタの指定 • ゼロで埋めるか 例 vaddpd zmm10, zmm20, zmm30 vaddpd zmm10, zmm20, zmm30, {rd-sae} vaddpd zmm10{k2}, zmm20, zmm30, {rd-sae} vaddpd zmm10{k2}{z}, zmm20, zmm30, {rd-sae} 25/30
  • 26.
    • とてもややこしい EVEX | EVEX| |0x62 P0 P1 P2| opcode ModRM/M [SIB] [Disp] [Imm] | 7 6 5 4 3 2 1 0 | P0 | R | X | B | R'| 0 | 0 | m | m | P1 | W | v | v | v | v | 1 | p | p | P2 | z | L'| L | b | V'| a | a | a | vaddpd [a4 a3 a2 a1 a0],[b4 b3 b2 b1 b0],[c4 c3 c2 c1 c0] 0x62|!a3 !c4 !c3 !a4 0001|1 b3 b2 b1 b0 101|0100 !b4 aaa| <code> |11 a2 a1 a0 c2 c1 c0| ... 26/30
  • 27.
    • zmm0=[8:7:6:5:4:3:2:1], [rax]= [2:1]のときどうなるか ゼロクリアクイズ(問題) 命令 意味 結果 movsd xmm0, xmm0 最下位doubleを移動 movsd xmm0, [rax] 最下位doubleを移動 vmovsd xmm0, xmm0, xmm0 最下位doubleを移動 vmovsd xmm0, [rax] 最下位doubleを移動 addsd xmm0, xmm0 最下位doubleを加算 addpd xmm0, xmm0 double x 2を加算 vaddsd xmm0, xmm0, xmm0 最下位doubleを加算 vaddpd xmm0, xmm0, xmm0 double x 2を加算 vaddpd ymm0, ymm0, ymm0 double x 4を加算 27/30
  • 28.
    命令 意味 効果 movsdxmm0, xmm0 最下位doubleを移動 [8:7:6:5:4:3:2:1] movsd xmm0, [rax] 最下位doubleを移動 [8:7:6:5:4:3:0:1] vmovsd xmm0, xmm0, xmm0 最下位doubleを移動 [0:0:0:0:0:0:2:1] vmovsd xmm0, [rax] 最下位doubleを移動 [0:0:0:0:0:0:0:1] addsd xmm0, xmm0 最下位doubleを加算 [8:7:6:5:4:3:2:2] addpd xmm0, xmm0 double x 2を加算 [8:7:6:5:4:3:4:2] vaddsd xmm0, xmm0, xmm0 最下位doubleを加算 [0:0:0:0:0:0:2:2] vaddpd xmm0, xmm0, xmm0 double x 2を加算 [0:0:0:0:0:0:4:2] vaddpd ymm0, ymm0, ymm0 double x 4を加算 [0:0:0:0:8:6:4:2] ゼロクリアクイズ(答え) • zmm0=[8:7:6:5:4:3:2:1], [rax] = [2:1]のときどうなるか • https://github.com/herumi/opti/tree/master/upper-zero 28/30
  • 29.
    • 512bit x32 = 2048byte = 2KiB! • コンテキストスイッチの度に全部保存? • xsaveopt, xsavec • Vol. 1 13章「Managing State Using the XSAVE Feature Set」 • メモリコピーを減らす仕組み • 各レジスタが変更されたか否かのビット情報を保持 • その情報にしたがって必要なものだけ退避する命令 レジスタの退避・復元 29/30
  • 30.
    • SIMDはテーブル引きに弱い • Haswellで登場(しかし使わない方が速いぐらいだった) •Skylakeでかなり改善された • AVX-512ではマスクレジスタが登場して大分使いやすくなっ た(ようだ) (おまけ)高速化されたgather命令 int tbl[16]; double a[16], b[16]; for (int i = 0; i < 16; i++) { b[i] = a[tbl[i]]; } vmovdqu zmm0, [rsp + tbl] kxnor k1, k0, k0 ; 全部1をたてる(c ← !(a ^ b)) vpgatherdd zmm2{k1}, [rax + zmm0 * 4] 30/30