次はZen 2のマイクロアーキテクチャであるが、基本的には従来のZenからほとんど変わっていない。Photo04はZenのパイプライン概略で、基本的なブロック図にはまったく変更がない、と筆者は想像する。今回、変更点として示されたのはフロントエンドの改良、それとFPUの帯域倍増だ。

  • Photo04:これは2016年のHotChipsにおけるプレゼンテーション。ただ、細かな数字は多少変わってくると思われる。しかしながら、例えばSkylakeの様にLoad/Store Unitが4つになったりはしていないと考えている

フロントエンド

まずフロントエンドの変更点は、Branch PredictionとPrefetch、それとOp-Cacheとなっている(Photo05)。

  • 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のほうは図を少し簡略化してあるという違いはあるが、ブロックの数そのものはやはり変わっていない。

  • Photo06:ZenのFPU構造。ADD/MULはいずれも128bit幅の演算器だが、256bit幅として動作はできない(組み合わせに制約がある)

  • Photo07:ぱっと見で違うのは、レジスタファイルの幅とLDCVTのバス幅がそれぞれ128bitから256bitになっただけ

説明は「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などの実装も現実的だろう。

  • Photo08:これはZenと(確か)Excavatorコアの比較だったと記憶している。おそらくInstuction Dispatchの数は6のままだろうが、Schedulerが100overとか、Retire Queueが200overになっても不思議ではない。また、いくつかの命令については高速化が図られているかもしれない

今回は記述がなかったのだが、筆者はL3キャッシュの構成がExclusiveからInclusiveに変更になったのではないか? とひそかに疑っている(理由は後述)。そしてInclusiveにした関係で、容量そのものは多少大容量化(図1でいうならCCXあたり10MB、図2ならばCCXあたり20MB程度)されているように思われる。