アセンブリ命令列を取得する。
echo 'int main(){ system("date");}' > test.c
gcc -S -masm=intel test.c
cat test.s
at test.s | head
.file "test.c"
.intel_syntax noprefix
.text
.section .rodata
.LC0:
.string "date"
.text
.globl main
.type main, @function
main:
実行ファイルからアセンブリ命令列を取得する(逆アセンブル)。 なお、これは直接コンパイルする方式のプログラムにのみ利用可能(JITコンパイル方式やバイトコードをコンパイルする方式では不可)。
gcc -xc - -otest <<< 'int main(){ system("date");}'
objdump -Mintel -d test| head
test: ファイル形式 elf64-x86-64
セクション .init の逆アセンブル:
0000000000001000 <_init>:
1000: f3 0f 1e fa endbr64
1004: 48 83 ec 08 sub rsp,0x8
1008: 48 8b 05 d9 2f 00 00 mov rax,QWORD PTR [rip+0x2fd9] # 3fe8 <__gmon_start__@Base>
フェッチステージ、デコードステージ、実行ステージを1命令ごとに繰り返す方式を「逐次実行」という。
クロックサイクルはステージを処理するサイクルで、これが短ければ短いほど多くの命令を処理できる。
なお、各ステージが1サイクルで完了するわけではなく、複数サイクルを経てステージの処理が完了する場合もある。
このときかかったサイクル数を「レイテンシ」という。
先行命令の完了を待たずに次の命令の処理を開始する方式を「パイプライン処理」という。
とはいえ、先行命令の結果が不確実な状態で次の命令を実行することは「投機的実行」に他ならない。
ただし先行命令にキャンセルが発生した場合、続く命令も正確にキャンセルしなければならない。
そうしないと致命的な脆弱性になってしまう。
SpectreとMeltdownはこのキャンセル処理の不適切さをついたもの。
先行命令と次の命令を同時に行う(例えば、先行命令と次の命令のフェッチと同時に行う)方式を「スーパースカラ方式」という。
ステージを細かく分割してクロックサイクル当たりの処理数を増やすことを「スーパーパイプライン化」「スーパーパイプライン方式」という。
また、スーパーパイプライン方式とスーパースカラ方式を組み合わせることもでき、現代のCPUはこの組み合わせた方式を採用している。
2つ以上の命令が、同一レジスタまたはメモリアドレスを参照する場合、それらの命令には「データ依存関係」がある。
特に「真のデータ依存関係」は、先行命令の書き込み結果を後続命令が参照する場合を指す。
真のデータ依存関係の場合は後続命令が待たされるため、「先行命令の実行完了」と「後続命令の実行開始」が逐次処理となる。
ただし、逐次処理となるのはあくまで「先行命令の実行完了」と「後続命令の実行開始」であり、先行命令の実行完了と後続命令のフェッチ開始が逐次処理になるわけではない点に注意。
スーパーパイプライン化において2以上のレイテンシが発生し、命令が実行できない空きサイクルを「ペナルティサイクル」という。
またパイプラインの流れを妨げる状況をハザードという。
真のデータ依存関係において、スーパースカラ方式でも逐次処理によって同時実行が妨げられる。
これらの妨げは「アウトオブオーダー実行」によって緩和できる場合がある。
アウトオブオーダー実行とは、真のデータ依存関係がない命令の開始を前倒しにする(つまりプログラムの順序と異なる順序で実行する)こと。
なお、プログラムの順序通りに命令を実行する方式を「インオーダー実行」という。
また、命令の実行開始を前倒しするにはフェッチおよびデコードが完了している必要がある。
アウトオブオーダー実行した場合でも、レジスタやメモリの内容をプログラムの順序通りに書き換えていく必要がある。
これを「再順序化(リオーダー)」といい、具体的には反映タイミングを制御するためのステージを設けることで実現している。
このステージを「命令コミットステージ」または「命令リタイアステージ」という。
加えて、リオーダーするためにプログラムのもともとの順序を記録しておく必要があり、これを記録するテーブルを「リオーダーバッファ」という。
なお、アウトオブオーダー実行可能な命令の数は、リオーダーバッファの長さに制限されることに注意する。
真ではないデータ依存関係も存在し、例えば先行命令が書き込んだレジスタと同じところに後続命令も書き込む場合や、先行命令が読み込むレジスタと後続命令が書き出すレジスタが同じ場合など。
この場合は、CPU内部の処理で別々のレジスタを使うようにリネームする。これを「レジスタリネーム」という。
分岐とは「特権レベルの変更を行わず」命令流を変えること。
分岐が発生する場合、アウトオブオーダー実行できない。 そのためキャンセルが発生した際の機会損失は、真のデータ依存関係の機会損失よりも大きくなる。
機会損失を緩和するためには分岐予測や、プログラムで分岐命令が発生しないような実装がある。
キャッシュメモリ(SRAM)の話。
キャッシュミスとキャッシュの階層化による緩和。
仮想記憶(仮想メモリ)の話。
仮想記憶によって、実際の主記憶(メモリ領域)よりも大きく見せたり、ソフトウェアにメモリを専有しているように見せる。
主記憶に格納されたページテーブルへのアクセスは遅いため、高速化のためにテーブルをコピーする「TLB(Translation Lookside Buffer)」というハードウェアがある。