次はZen 2のマイクロアーキテクチャであるが、基本的には従来のZenからほとんど変わっていない。Photo04はZenのパイプライン概略で、基本的なブロック図にはまったく変更がない、と筆者は想像する。今回、変更点として示されたのはフロントエンドの改良、それとFPUの帯域倍増だ。
フロントエンド
まずフロントエンドの変更点は、Branch PredictionとPrefetch、それとOp-Cacheとなっている(Photo05)。
Branch Predictionについては、ZenではPerceptionベースの分岐予測が実装されており、これが改良されたものと思われる。実のところこのPerceptionベースの分岐予測は、Microcodeで実装されているため(ただし、あとからの変更は不能)、変更そのものはそれほど難しくないだろう。
Prefetchの改良は、それこそLlanoの時代からずっと続いており、Zen 2でも改良が行われた模様だ(“Better Instruction Pre-Fetching”と説明されているが、具体的に何をどうBetterにしたかは現状不明。まさかPerceptionベースの実装が行われた、というわけでもないと思うが)。
最後がOp-Cacheで、Op-Cacheそのものの構造に手が入ったほか、“Larger Op Cache”と説明されており、キャッシュサイズの大容量化も図られた模様だ。このOp CacheはL0として動くことになるが、RYZENの場合で言えば、2KBあたりまではL0の効果があり、4KBではもう効果がないことが分かっている(【レビュー】「Ryzen Threadripper 2」深層レビュー - こんなに違うXシリーズとWXシリーズ)。Zen 2ではこれがもう少し大きくなるので、4KBくらいまでL0として動作するかもしれない。
FPU
もう1つの改良点はFPUである。もともとはこんな構造(Photo06)だったわけだが、これがPhoto07のように変わった形だ。Photo07のほうは図を少し簡略化してあるという違いはあるが、ブロックの数そのものはやはり変わっていない。
説明は「Doubled Floating Point Width to 256-Bit」である。実はこの「Doubled」をどう解釈するかはちょっと難しい。可能性は2つあり、
・4つのFPUの処理幅はすべて128bit。ただしMUL0とMUL1、ADD0とADD1が完全に同じ機能を持つ対称型の構成になり、トータルで256bitの演算を1サイクルで実施できる
・4つのFPUは、非対称構成のまま256bit化された
となる。
もともとZenのFPUは非対称構成になっていた。以下の表1は、Zenコアの場合にFPU命令がfp0~fp3という4つのFPUユニットのどこで実行可能かをまとめたものだ。
■表1 | ||
---|---|---|
命令 | Load/Store | fpu unit |
fp_mov_direct_load | load | fp3 or fp1 |
fp_mov_direct_store | store | fp2 or fp3 |
fp_mov_double | fp3 | |
fp_mov_double_load | load | fp3 |
fp_mov_direct | fp3 | |
fp_spc_direct | fp2+fp3 | |
fp_fsgn | fp3 | |
fp_fcmp | fp0+fp2 | |
fp_fcmp_load | load | fp0+fp2 |
fp_op_mul | fp0 | |
fp_op_mul_load | load | fp0 |
fp_op_imul_load | load | fp0 |
fp_op_div | fp3 | |
fp_op_div_load | load | fp3 |
fp_op_idiv_load | load | fp3 |
fp_insn | fp0 or fp1 or fp2 or fp3 | |
mmx_add | fp0 or fp1 or fp3 | |
mmx_add_load | load | fp0 or fp1 or fp3 |
mmx_cmp | fp0 or fp3 | |
mmx_cmp_load | load | fp0 or fp3 |
mmx_cvt_pck_shuf | fp1 or fp2 | |
mmx_cvt_pck_shuf_load | load | fp1 or fp2 |
mmx_shift_move | fp2 | |
mmx_shift_move_load | load | fp2 |
mmx_move_store | store | fp2 |
mmx_mul | fp0 | |
mmx_load | load | fp0 |
avx256_log | fp0 or fp1 or fp2 or fp3 | |
avx256_log_load | load | fp0 or fp1 or fp2 or fp3 |
sse_log | fp0 or fp1 or fp2 or fp3 | |
sse_log_load | load | fp0 or fp1 or fp2 or fp3 |
avx256_log1 | fp1 or fp2 | |
avx256_log1_load | load | fp1 or fp2 |
sse_log1 | fp1 or fp2 | |
sse_log1_load | load | fp1 or fp2 |
sse_comi | fp0 or fp1 | |
sse_comi_load | load | fp0 or fp1 |
sse_comi_double | fp0 or fp1 | |
sse_comi_double_load | load | fp0 or fp1 |
sse_test | fp1 or fp2 | |
sse_test_load | load | fp1 or fp2 |
sseavx_mov | fp0 or fp1 or fp2 or fp3 | |
sseavx_mov_store | store | fp0 or fp1 or fp2 or fp3 |
sseavx_mov_load | load | fp0 or fp1 or fp2 or fp3 |
avx256_mov | fp0 or fp1 or fp2 or fp3 | |
avx256_mov_store | store | fp0 or fp1 or fp2 or fp3 |
avx256_mov_load | load | fp0 or fp1 or fp2 or fp3 |
sseavx_add | fp2 or fp3 | |
sseavx_add_load | load | fp2 or fp3 |
avx256_add | fp2 or fp3 | |
avx256_add_load | load | fp2 or fp3 |
sseavx_fma | (fp0+fp3) or (fp1+fp3) | |
sseavx_fma_load | load | (fp0+fp3) or (fp1+fp3) |
avx256_fma | (fp0+fp3) or (fp1+fp3) | |
avx256_fma_load | load | (fp0+fp3) or (fp1+fp3) |
sseavx_iadd | fp0 or fp1 or fp3 | |
sseavx_iadd_load | load | fp0 or fp1 or fp3 |
avx256_iadd | fp0 or fp1 or fp3 | |
avx256_iadd_load | load | fp0 or fp1 or fp3 |
ssecvt | fp3 | |
ssecvt_load | load | fp3 |
ssediv_ss_ps | fp3 | |
ssediv_ss_ps_load | load | fp3 |
ssediv_sd_pd | fp3 | |
ssediv_sd_pd_load | load | fp3 |
ssediv_avx256_ps | fp3 | |
ssediv_avx256_ps_load | load | fp3 |
ssediv_avx256_pd | fp3 | |
ssediv_avx256_pd_load | load | fp3 |
ssemul_ss_ps | fp0 or fp1 | |
ssemul_ss_ps_load | load | fp0 or fp1 |
ssemul_avx256_ps | fp0 or fp1 | |
ssemul_avx256_ps_load | load | fp0 or fp1 |
ssemul_sd_pd | fp0 or fp1 | |
ssemul_sd_pd_load | load | fp0 or fp1 |
ssemul_avx256_pd | fp0 or fp1 | |
ssemul_avx256_pd_load | load | fp0 or fp1 |
sseimul | fp0 | |
sseimul_avx256 | fp0 | |
sseimul_load | load | fp0 |
sseimul_avx256_load | load | fp0 |
sseimul_di | fp0 | |
sseimul_load_di | load | fp0 |
sse_cmp | fp0 or fp1 | |
sse_cmp_load | load | fp0 or fp1 |
sse_cmp_avx256 | fp0 or fp1 | |
sse_cmp_avx256_load | load | fp0 or fp1 |
sse_icmp | fp0 or fp3 | |
sse_icmp_load | load | fp0 or fp3 |
sse_icmp_avx256 | fp0 or fp3 | |
sse_icmp_avx256_load | load | fp0 or fp3 |
例えば、fp_mov_direct_loadであればfp1かfp3のどちらかで実行可能だし、fp_fcmpであれば、fp2とfp3を両方同時に利用して実行するという意味になる。そしてload/storeは、同時にload/storeユニットが動くか否か(動くとしたらどちらか)を示したものである。こうしてみると、各々のFPUユニットで、割と機能が偏っていることが分かる。
さて、従来問題とされたのは、例えばsseavx_fmaやavx256_iaddなどの命令だ。これらの場合、「(fp0+fp3)or(fp1+fp3)」という構成になっているのが分かる。つまりこれを実行する場合、fp3が共用になるので、一度に2命令を処理することができない。
これが「(fp0+fp2)or(fp1+fp3)」とか「(fp0 or fp1)+(fp2 or fp3)」という構成であれば、1サイクルで2命令を実行できることになる。各々のFPUユニットの幅は128bitだから、トータルで256bitになるわけだが、Zenではfp2に実装されている機能が偏っており、avx256命令を1サイクルで処理することが不可能となっていたわけだ。
このあたりがZen 2でどう変わったか……だが、結果から言えば「4つのFPUは、非対称構成のまま256bit化された」と思われる。理由はgccのZen 2用Patchだ。
実は上記の表1の元ネタは、AMDのVenkataramanan Kumar氏(AMD内部でgcc関連の作業に携わるエンジニア)が、Zenコアが登場する前の2015年10月6日投稿した、gccをZenに対応させるためのPatchである。
このPatchには gcc/config/i386/znver1.md というファイルの追加も含まれており、この中にFPU命令をどうFPUユニットに割り当てるかの詳細が記されている。それを見やすくまとめたのが表1だ。
さてそのKumar氏、2018年11月4日に、今度はZen 2(znver2)向けgccのPatchを投稿した。ところがこちらのPatchを確認すると、znver1.mdへの修正はなく、またznver2.mdといったファイルの追加もない。
ということは、FPU命令をどうFPUユニットに割り当てるか、という部分ではZen 2でまったく変更なしという結論に達する。要するに非対称構成はそのまま、というわけだ。となると256bit化を実現するためには、全FPUユニットを256bit幅にするしかない。
同様にLoad/Storeユニットに関しても、Zen 2では例えば数が4つに増えたという形跡は、Patchからは伺えない。AGUの数は見る限りは2つのままで、ここから考えるに、単純にLoad/Storeユニットも256bit化されただけ、と考えるのが妥当と思われる。
ちなみに説明はないが、諸々のパラメータ(Photo08)の強化はあるだろうし、TLBの容量などにも手が入っている可能性がある。従来だと性能改善とトランジスタ数の増加を勘案してギリギリのところでキューの容量などを定めていたが、14nm → 7nmで利用できるトランジスタ数にゆとりができたため、バランスするポイントが変化している。これまでより深いキューとか、より大容量のTLBなどの実装も現実的だろう。
今回は記述がなかったのだが、筆者はL3キャッシュの構成がExclusiveからInclusiveに変更になったのではないか? とひそかに疑っている(理由は後述)。そしてInclusiveにした関係で、容量そのものは多少大容量化(図1でいうならCCXあたり10MB、図2ならばCCXあたり20MB程度)されているように思われる。