トップ «前の日(04-08) 最新 次の日(04-10)» 追記

U-memo

2006|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|08|
2009|08|10|
2010|02|03|
2011|11|12|
2012|04|
2016|02|
All= / Today= / Yesterday=

2008-04-09

_ [プロセッサ] Intel x86 CPU の最近の命令フェッチ機構

(Ando's Processor Information の「最近の話題 2008年4月5日」経由)

ここでは LSD (Loop Stream Decoder) の話題を拾う。 そもそも実装されたのは Core2 (Conroe/Merom世代)。

たとえば IBM の POWER シリーズプロセッサのような、ある命令フェッチ機動と同時に分岐予測まで行って次のサイクルには分岐先アドレスを予測できる構造を取っている場合には(省電力をのぞいて)LSDなんて考え方は不要。 分岐予測のロスが無いんだから。

# とはいえキャッシュラインを跨ぐケースはあるので全くロスがないわけでは無い。やれば効果はある(はず)。

分岐予測のため野記憶バッファが大きくて、分岐先アドレスの予測が即座にできない場合に効果が出てくる。特に

loop:
 mov eax,[ecx]
 add ecx,1
 cmp eax,0
 jnz loop
 ...
(命令はとてもてきとう)

のような短いループ構造の場合、 実行系は1ループが1サイクルのスループットで並列処理が出来てしまう のに対し、分岐予測は (命令フェッチアドレス生成 -> 分岐予測アドレス生成) に2サイクル掛かるとすると、分岐予測先のアドレスを待つために(毎回同じアドレスなのにね!)、実行系の半分のスループットしか出ないことになる。 つまり命令フェッチ側がボトルネックになる。

# パイプラインを書かないとイメージしにくいかも。

ゆえに、「(短い)ループと分かっているなら」分岐予測を省いてしまってその後ろのバッファからばがばかと繰り返し供給してやればよい、となる。 x86 のような CISC マシンはデコードも重いので Nehalem ではデコーダの後ろにまで持っていっただけ。

というのが基礎的な話題。

しかし触れられていない厄介な話題として、CISC アーキテクチャ特有の話として、命令書き換え(が可能)がある。

# RISC CPUは大抵は明示的に専用のフラッシュ命令を実行しない限りは命令書き換えしたことを反映しなくていいことになっているので、問題は限定的。

loop:
 mov [ecx],0x90909090  ; 0x90 は nop の機械語コード
 add ecx,100
 jmp loop
 ...

たまたま 0x90 をメモリに書いたその先がこのループ命令列の jmp 命令 だった場合、 (条件はあるものの) jmp ではなく書き換えた後の nop を実行する(つまりループが終わる) 必要がある。

毎回命令フェッチを実行することでキャッシュにとりに行っていれば、 書き換えも反映されるはず(というようにキャッシュは設計されている)わけだが、 LSD の仕掛けではデコーダーの前後にある命令バッファを書き換えてやるとか、パイプラインをフラッシュして命令フェッチからやりなおすとか、してやらないといけない (命令セットアーキ違反になる)。 制御の方法はいくつか考えられるけれど、これは結構困難なことだったりする。

単純にアドレスを覚えて一致検出をするにしても、仮想アドレス(Virtual Address) で命令フェッチするのに、仮想アドレスが違うのにたまたま物理アドレス(Physical Address) が一致しているようなところに書き込まれた ケースまで含めて書き換えを認識する必要があるのだ。

MMU は大抵キャッシュ側にくくりつけられていて命令バッファの近くにはないので、面倒臭いことこの上ない。かといって大雑把にやりすぎると逆に足を引っ張るはずなのだが...。

# 単純にストア命令がいるときはあきらめるというのが手っ取り早い解決方法ではある。それでも周辺に影響は有るが。