5人で1分に1杯のラーメン
小さなラーメン店で、店主が1人で、注文、麺ゆで、盛りつけ、運ぶ、会計をしています。お客は1杯を5分待ちます。
そこで4人を雇い、1人に1つの仕事を任せました。1杯を作るのには、今も5分かかります。けれど、できあがった1杯が1分ごとに出てくるようになりました。プロセッサも、命令について同じことをします。
このレッスンのゴール
メモ
パイプラインがプロセッサを速くする仕組みと、それを止めるものを説明することができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| パイプライン | 複数の命令の段階を、別々の段で同時に進めること | |
| ステージ | パイプラインの1つの段。1クロックかかる | EX |
| ハザード | 次の命令が予定どおりに進めなくなる原因 | |
| ストール | ある段が待つだけで、仕事をしない1サイクル | |
| フォワーディング | 書き戻す前の結果を、次の命令へ直接渡すこと | |
| 分岐予測 | 分岐がどちらへ進むかを、決まる前に推測すること |
学生が入力するセルは、このプレビューでは空白です。
5つのステージに、5つの命令
基本的な RISC-V のパイプラインには、5つのステージがあります。IF は命令の取り出し、ID はデコードとレジスタの読み出しです。EX は計算、MEM はメモリの読み書き、WB はレジスタへの書き戻しです。
どのステージも1クロックかかります。ある命令が EX にいるあいだ、次の命令は ID に、その次は IF にいます。だから最大5つの命令が同時にプロセッサの中にあります。
例: ハザードのない3つの命令
| 命令 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| addi t0, zero, 4 | IF | ID | EX | MEM | WB | ||
| addi t1, zero, 5 | IF | ID | EX | MEM | WB | ||
| addi t2, zero, 6 | IF | ID | EX | MEM | WB |
学生が入力するセルは、このプレビューでは空白です。
アーキテクチャのフォルダへ移る
cd ~/archここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
1つの命令は速くならないのに、速い
1つずつ順に行うと、5サイクルの命令60個に300サイクルかかります。パイプラインでは、最初の1個が5サイクルで終わり、あとは1サイクルごとに1個ずつ終わります。だから60個で 5 + 59、つまり64サイクルです。
1つの命令が始まってから終わるまでは、今も5サイクルです。増えたのは、1サイクルに終わる命令の数です。ただし、それは命令を待たせるものがないあいだだけです。
まだできていない結果
ハザードとは、次の命令が予定どおりに進めない理由です。よくあるのはデータハザードで、前の命令がまだ書き終えていないレジスタを、次の命令が必要とする場合です。
何も工夫しなければ、値が書き戻されるまでパイプラインはストールします。フォワーディングを使うと、EX の答えを次の命令へ直接渡せるので、待ちのほとんどが消えます。ロードは別です。値は MEM のあとでしかできないので、すぐ次の命令は1サイクル待ちます。
例: このモデルのストールの数
| 前の命令 | 使う命令との距離 | フォワーディングなし | フォワーディングあり |
|---|---|---|---|
| add か addi | 1つ前 | 2 | 0 |
| add か addi | 2つ前 | 1 | 0 |
| lw | 1つ前 | 2 | 1 |
| lw | 2つ前 | 1 | 0 |
| どれでも | 3つ以上前 | 0 | 0 |
学生が入力するセルは、このプレビューでは空白です。
ロードユース・ハザードのあるプログラムを書く
printf '.globl _start\n_start:\n addi sp, sp, -16\n li t0, 20\n sw t0, 8(sp)\n lw a0, 8(sp)\n add a0, a0, a0\n li a7, 93\n ecall\n' > use.sここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
アセンブルする
riscv64-linux-gnu-as use.s -o use.oここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
リンクする
riscv64-linux-gnu-ld use.o -o useここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
エミュレータで実行する
qemu-riscv64 ./useここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
終了ステータスを読む
echo $?ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
機械語を読む
riscv64-linux-gnu-objdump -d useここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
「パイプラインにすると、1つ1つの命令が速く終わる」と考える人が多くいます。そうではありません。どの命令も、今も5つのステージを全部通ります。パイプラインは、1サイクルに終わる命令を増やします。ストールはその得を捨てるので、命令の順番が大切になります。
すき間はコンパイラが埋める
コンパイラは、ストールの決まりを知っています。ロードのすぐあとに、その値を使う命令が来るとき、値を使わない別の命令を探して、あいだへ移します。
次の Run は、ラボの loop.c を RISC-V 向けにコンパイルします。列ごとに足すループ .L9 を見て、ロードと、それを使う add のあいだに何があるかを探しましょう。
loop.c を基本の RISC-V にする
riscv64-linux-gnu-gcc -O2 -march=rv64gc -S loop.cここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
列ごとに足すループ
grep -A 4 'L9:' loop.sここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
分岐の行き先を推測する
分岐はコントロールハザードを起こします。EX で条件が決まるまで、パイプラインは次に何を取り出すか分かりません。分岐のたびに待つと、サイクルが無駄になります。
そこでプロセッサは分岐予測を使います。推測して、そのまま進めるのです。当たれば損はなく、外れれば取り出した仕事を捨ててやり直します。ループの最後の bne はほとんど毎回戻るので、推測しやすい分岐です。
さらに挑戦
次のプログラムがあります。lw t0, 0(a0)、add t2, t0, t0、lw t1, 4(a0)、add t3, t1, t1、addi a0, a0, 8。課題の関数で、フォワーディングありのストールを数えてください。
次に、答えを変えずにストールが0になるよう、5行を並べかえます。addi a0, a0, 8 を前へ動かしてはいけない理由も説明しましょう。