Previous ToC Next

142. 相互作用計算におけるレイテンシとレジスタ数 (2018/8/8)

単純な重力(ないし静電)相互作用計算(dx 等は3次元ベクトルで内積があるとして)

   dx = xj-xi
   r2= dx*dx
   r=sqrt(r2)
   mr3inv=mj/(r*r*r)
   fi += mr3inv*dx
みたいなコード(これはループの内側のみ)を考えます。xi, fi はレジスタにあるものとして、また簡単のため 逆数平方根命令はあって、加算・乗算と同じスループットでできるとして、 命令列は

 1  ld xjx
 2  ld xjy
 3  ld xjz
 4  ld mj
 5  dx = xjx-xix
 6  dy = xjy-xiy
 7  dz = xjz-xiz
 8  r2 = dx*dx
 9  r2 += dy*dy
 10  r2 += dz*dz
 11  rinv = rsqrt(r2)
 12  r2inv = rinv*rinv
 13  mrinv = mj*rinv
 14  mr3inv = r2inv*mrinv
 15  fix += mr3inv*dx
 16  fiy += mr3inv*dy
 17  fiz += mr3inv*dz
というような感じでしょう。演算器が FMA 1 つ、ロードまたはストアが演算 命令と並行して発行できる、逆数平方根とFMAは同時発行できない、というよ うな実装だとして、さらに単純化のため命令のレイテンシは全て m サイクル だとしましょう。依存関係は

  1->5
  2->6
  3->7
  5->8
  6,8->9 
  7,9->10
  10->11
  11->12
  4,11->13
  12,13->14
  5,14->15
  6,14->16
  7,14->17
となります。今まだアンロールとかは考えないとすれば、 必要なレジスタ数は、

  xi:3
  fi:3
  dx:3
  あと  2こで多分なんとか
で 11個はアーキテクチャレジスタとして必要そうです。ここで、アーキテク チャレジスタが無限に沢山あるなら、最内側ループを(i側でもj側でもどちら でもよいですが)m重に展開すれば、

 11  ld xjx1
 12  ld xjx2
     ....
 1m  ld xjxm
 21  ld xjy1
 ..
というふうに命令を並べることができます。そうすると、 これは、m並列でレイテンシ1(次のサイクルで結果が利用できる)命令とみなす ことができますから、

 (1  ld xjx)
 2  ld xjy; dx = xjx-xix
 3  ld xjz; dy = xjy-xiy
 4  ld mj; dz = xjz-xiz
 5  r2 = dx*dx
 6  r2 += dy*dy
 7  r2 += dz*dz
 8  rinv = rsqrt(r2)
 9  r2inv = rinv*rinv
 10  mrinv = mj*rinv
 11  mr3inv = r2inv*mrinv
 12  fix += mr3inv*dx
 13  fiy += mr3inv*dy
 14  fiz += mr3inv*dz(; ld xjx)
となって 13m サイクルでm回ループを回せることになります。演算器がFMAで なくて乗算・加算が独立かつ並列に発行可能ならもう2サイクルほどつめられ る気もしますが、FMAであればこれ以上サイクルをつめることは原理的に不可 能でしょう。(多分)

これで m=6 くらいの常識の範囲のレイテンシで、演算器1つだけでも、アーキ テクチャレジスタだけで理想的な性能を実現しようと思うと結構な数がいるこ とがわかります。数が足りないと、展開できるループ数が 減るわけですが、それが明示的にストールサイクルとして見えるのは、 まずは直接の依存がある、上の番号では 7-8-9-10-11-12 のチェインに なります。それ以外は、1命令スロットはあるので m/2 重にアンロール できていれば依存関係は見えません。そうすると、必要なサイクル数は (ちょっと誤差はありますが)n重アンロールで 8n + 5m くらいになって、 理想的な場合に比べた実行効率は 13n/(8n+5m) まで低下します。 n=m/2 とすれば 70%程度です。

例えばm=6で2重にしかアンロールできなかったとすると、 もとの命令列で 5-8(1サイクル)、 8-9-10-11 のチェイン(それぞれ2サイクル)、 11-13(1サイクル)、13-14-15のチェイン(それぞれ2サイクル) で(これはそれぞれ実際のストールの半分)、合計12サイクルのストール がおこって実行効率は 13/25=52%まで落ちることになります。

演算器が2つになると、この話はどうなるのか、というと、基本的は ループ展開を2倍しないといけなくなってレジスタがさらに2倍いる だけです。なので、スーパースカラー実行ユニットをもつ計算機でちゃんと性 能をだすためには非現実的な数のレジスタが必要になります。これは、 スーパースカラー演算ユニットがそもそも本来は「おまけ」で、2つ実行でき る時には実行、という思想だったからというところがあります。つまり、 演算器1個の時の2倍の性能ではなくて 1.x 倍程度を期待しているからです。

しかし、マルチコアとか電力性能とか言い出すと演算器2倍つけて性能が例えば 1.3倍とかで電力2倍では話にならないわけで、演算器2個もつ意味は再考され るべきでしょう。

逆に、SMT を考えると、例えば4 way の SMT ならそもそもループを4重に展開 しているのと同じなので、アーキテクチャレジスタの数は 1/4 でいいことに なります。物理的にはもちろん4倍の語数がありますが、SMT の場合にはマルチ ポートレジスタファイルを使わない実装も可能なので電力・面積的には有利に なります。

とはいえ、多くのアーキテクチャでアーキテクチャレジスタの数は 16 とか32 で、演算器は2個あったりレイテンシは6よりもっと大きかったりします。

AVX なんかだと命令セット的にはロードストアではなくて、メモリオペ ランドをとるので、見かけ上アーキテクチャレジスタを喰わない命令を実行時 になんとかすることができると思いますが、ロードストアアーキテクチャでは どうしてもアーキテクチャレジスタを消費します。

例えば、アーキテクチャレジスタが32、FMAユニットが2、レイテンシが6だと どうなるかを考えてみます。コンパイラでは2重にアンロールしかできないの で、これは本質的にレジスタ16個でFMAユニットが1の場合と変わりません。 なので、以下、アーキテクチャレジスタ16、FMAユニット1、レイテンシ6を考 えます。アンロールするレジスタの余裕はないので、コンパイラは

 1  ld xjx
 2  ld xjy
 3  ld xjz
 4  ld mj
 5  dx = xjx-xix
 6  dy = xjy-xiy
 7  dz = xjz-xiz
 8  r2 = dx*dx
 9  r2 += dy*dy
 10  r2 += dz*dz
 11  rinv = rsqrt(r2)
 12  r2inv = rinv*rinv
 13  mrinv = mj*rinv
 14  mr3inv = r2inv*mrinv
 15  fix += mr3inv*dx
 16  fiy += mr3inv*dy
 17  fiz += mr3inv*dz
というコードをだすしかありません。実行のクリティカルパスは 1-5-8-9-10-11-12-13-14-15 なので54サイクルはかかるはずで、 完全にパイプラインが埋まると13サイクルですむのに比べて 1/4 まで 性能が落ちます。人がアセンブラ書くなら、2重にアンロールしてレジスタス ピルを可能な範囲で隠蔽すれば性能2倍にはならないですが1.6倍くらいには なるかもしれなくて、性能低下は 75% ではなくて 60% 程度にできるかもしれ ません。

さて、アウトオブオーダー実行とレジスタリネーミングの効果はどうでしょう か?命令デコードが無限に速いとすれば、 ループm回転分を一気にデコードして命令キューにいれることができれば、 最後の力の積算の手前まではパイプラインを埋めることができます。 ここでパイプラインが空きますが、そこには次のm回転の命令をいれれば いいのですが、そうすると命令キューやリネーミングレジスタ資源は m回転分では足りないことがわかります。さらに、命令デコードはもっとずっ と遅いので、そこでも大きなロスがあります。

つまり、アーキテクチャレジスタであれば 11m 個あれば、貧弱なデコーダで もパイプラインを完全に埋めることができるのですが、アウトオブオーダー 実行とレジスタリネーミングに頼るとそれでは足りない、ということがわかり ます。ちなみに、もちろんFMA2ユニットなら必要なOoOリソースはさらに倍に なります。

Xeon とか Xeon Phi (KNL) では、メモリオペランドをとる命令があることで アーキテクチャレジスタを消費しないで物理レジスタを使えること、比較的演 算レイテンシが小さいこと、また Xeon Phi ではSMTで実効的なレイテンシを 下げていること、膨大な OoO資源を投入したことで比較的高い実行効率を実現 できていますが、そのために電力・面積あたりの性能はあまり高くなっていま せん。

絶対的にレジスタ数が足りない時に、多少でもパイプラインが埋まるようにす る方法は、「ループ分割」です。例えば、 r2 まで計算したところで一旦r2 と、場合によっては dx, dy, dz もストアしてループ本体を短くするわけです。 次は mr3inv までだけを計算するループにするとかですね。 そうすると、ループ本体で必要なレジスタ数が減るので、レイテンシは隠蔽で きるようになる一方、ロード・ストアが大幅に増えるのでそちらのオーバーヘッ ドが発生します。

以上のような問題は、以下のどれによっても解決できます。

  1. アーキテクチャレジスタを増やす
  2. SMT にする
  3. ベクトル命令にする

1, 2 は自明ですが、 3 は、結局

 11  ld xjx1
 12  ld xjx2
     ....
 1m  ld xjxm

というようなコードをだすくらいならはじめから m サイクルで m個の連続したデータをロードする「命令」になってればいいではないか、 というものです。これにある程度近いことが実現できるのは、 ARM の SVE 命令です。 SVE は命令コードは同じで命令のベクトル長を 128ビット から 2048ビット まで変化させられるので、 2048ビットの命令を256ビットの演算器で8サイクル毎に実行する、と いった実装が論理的には可能です。

消費電力を考えると、命令デコードのコストは決して馬鹿にはならないので、 そのコストを下げることができるベクトル命令の実装は望ましいことではあり ます。

なお、演算器が32個とか64個とかになってくると、色々なプログラムで最内側 ループ長がそもそもそんなにないという問題は発生します。実際にはレイテン シを掛けたくらいのループ長が必要なので、例えば 64演算器でm=8 なら最低 でも256、実際には512とか1024とかないと性能がでないわけです。粒子間相互 作用でも、短距離力とか、あるいはツリー法等で相互作用リストが短いと、普 通に最内側ループをSIMD化するのでは性能がでない、ということになります。

問題自体の並列度は普通はもっとあるので、これは、並列化の方向を変えるこ とで原理的には解決できます。但し、そのためにはSIMD 命令で間接アクセス があることが望ましい(というか、それがないと大幅に性能が落ちる)ことが多 いですが、しかし、現在の SIMD 命令の実装では間接アクセスで性能をだすの は原理的困難があるので、あまり上手くいかない、ということになります。

間接アクセスで性能がでないのは、 SIMD で間接アクセスするには1サイクル で沢山アドレスがでてそれぞれに対応したデータにアクセスできないと いけないですが、単一の物理メモリユニット(キャッシュでも)はそんな沢山の アドレスが入る仕掛けになってないからです。マルチバンクにして沢山アドレスがは いるようにしたとすると衝突を解決するために複雑な機構が必要であり性能も 落ちます。

これを解決するには、 SIMD 演算器が単一の物理メモリやキャッシュを アクセスするのではなく、演算器毎にパーテッションをきった分散メモリにして それぞれの中だけでの間接アクセスですむようなデータ構造にすればよいとい うことになります。

PEZY-SCx では、そもそもSIMDにしないことで独立なアクセスを可能にしてい て、このためコア内ワイドSIMDの問題を回避でき、さらに4way SMT によって レジスタ不足の問題も解決されています。効率をだす、という観点からはよく 考えられたプロセッサだといえるのではないでしょうか?

もちろん、これは粒子間相互作用のようなコンパクトな演算カーネルだからで、 陽解法差分法流体計算のような巨大なカーネル(特にデータ再利用を効率的に 行おうとしたとき)には別の問題が発生します。

なお、ここではあえてふれていない現在開発中のプロセッサについては、 ここにシミュレータを使った詳しい評価があります。
Previous ToC Next