目次
序文
前のブログ:グループ実験3Mipsアドベンチャーのパイプラインアドベンチャー
半月触れた。。。カッコウは計算グループの実験4を書くためにここにいます
この実験は非常に簡単ですが、少し難しいです。
この実験は誤算される可能性が高いか、シミュレータの予期しない結果によって行われたため、今日は実験を詳細に記録して分析します。
注:この記事の[ブロッキング期間の計算方法]の部分を必ずお読みください。この部分は結論です。実験の周期の計算は、それを読むことで簡単に理解できます。
実験内容
以下の実験手順と手順に従って、関連する操作を完了し、実験プロセスのスクリーンショットを記録します。
- まず、行列乗算のコードを1つ与え、BTB関数をオンにして最適化し、パイプラインの詳細を観察して、その中でのBTBの役割を説明します。
- 次に、BTBがオンになっていても無効になるコードを設計します。
- 第三に、ループ展開法を用いて、分岐による流れの停止回数が減少する現象を観察し、BTB構造を用いた場合の分岐による流れの停止回数を比較します。
(オプション:x86システムでC言語の行列乗算コードを記述し、perfを使用して分岐予測の失敗の数を観察し、その数が知識と一致するかどうかを分析します。次に、2番目の部分で使用するコードを記述して分岐予測を失敗させます。 x86が正しく予測できることを確認し、説明してみてください)
背景知識
ジャンプステートメントに遭遇した場合、この命令がジャンプするかどうかを判断するためにMEMステージまで待つ必要があることがよくあります(ハードウェアの最適化により、分岐遅延を大幅に短縮し、分岐実行をIDステージに進めることができます。分岐予測エラーのコストは1つの命令だけに削減されます)
正しい命令がプリフェッチされることを保証するためのこの遅延は、制御ハザード(ブランチハザード)と呼ばれます。制御ハザードによって引き起こされるパフォーマンスの低下を減らすために、分岐予測手法が一般的に使用されます。
分岐予測技術
分岐予測テクノロジには、コンパイル時の静的分岐予測と実行時の動的分岐予測が含まれます。ここでは、動的分岐予測にBTB(Branch Target Buffer)テクノロジーを導入することに焦点を当てます。
BTBは分岐ターゲットバッファであり、分岐命令(対応する命令アドレス)をバッファに入れて保存します。次回同じ命令(ジャンプ判定)が発生すると、前回と同じように実行されます。ジャンプ(分岐)分岐しないかどうか)予測。
実行可能なBTB構造の概略図は次のとおりです
。BTBが採用された後、パイプラインの各段階で実行される関連操作は次のとおりです
。BTBを入力または削除するには、2サイクルが必要です。
この文は、コードのブロッキングサイクル数の後続の計算にとって非常に重要です。!!BTBブランチの確立とエラーには2サイクルかかります。
ブロッキング期間の計算方法⭐
私のような多くの子供靴は、ループコードが初めて実行されるときにブロックされるサイクルの数について非常に混乱していると思います。
したがって、要約したブロッキングサイクルの数を以下に直接示します。この実験のループコードフレームワークを例として取り上げます(つまり、beqはループの最後に配置され、ループラベルに戻る必要があるかどうかを判断します)
BTBを開かないでください
BTBを開始しない場合、mipsはデフォルトでジャンプしないと予測します。n回のループでは、ループを終了する最後の時間を除いて、beqが正しく予測されるため、n-1個のBranch TakenStallが発生します。常に予測が間違っています。そして、各Branch TakenStallは1サイクルをブロックします
したがって、ブロックされたサイクルの数=(n-1)* 1 = n-1サイクル
BTBをオンにする
BTBをオンにすると、n回のループで、1つのブランチテイクストールと1つのブランチ誤予測ストールが発生します。
BTBがループ内で初めて確立されていないため、Branch TakenStallsが1回発生しました
さらに、前回BTBがジャンプすると予測していたため、前回ループを終了したときのbeqで、前回予測エラーが発生したときにBTBをクリアする必要があり、Branch MispredictionStallsが発生しました。
ブランチ誤予測ストールとブランチ誤予測ストールに関係なく、BTBの修正には2サイクルかかるため、ブロックされたサイクルの最終的な数= 2
パート1行列の乗算と最適化
この段階で、最初に行列乗算の例を示し、次にパイプラインをBTB関数なしで直接実行するように設定し(configure-> enable branch target buffer)、結果を観察して記録します。
次に、BTB機能をオンにして再度実行し、実験結果を観察します。2つの実験の結果を比較して、BTBが機能するかどうかを確認します。機能する場合は、パイプライン実行の詳細を観察し、BTBが機能する理由を説明します。
行列乗算のコードは次のとおりです。
.data
str: .asciiz "the data of matrix 3:\n"
mx1: .space 512
mx2: .space 512
mx3: .space 512
.text
initial: daddi r22,r0,mx1 #这个initial模块是给三个矩阵赋初值
daddi r23,r0,mx2
daddi r21,r0,mx3
input: daddi r9,r0,64
daddi r8,r0,0
loop1: dsll r11,r8,3
dadd r10,r11,r22
dadd r11,r11,r23
daddi r12,r0,2
daddi r13,r0,3
sd r12,0(r10)
sd r13,0(r11)
daddi r8,r8,1
slt r10,r8,r9
bne r10,r0,loop1
# i=r17, j=r18, k=r19
mul: daddi r16,r0,8
daddi r17,r0,0
loop2: daddi r18,r0,0 #这个循环是执行for(int i = 0, i < 8; i++)的内容
loop3: daddi r19,r0,0 #这个循环是执行for(int j = 0, j < 8; j++)的内容
daddi r20,r0,0 #r20存储在计算result[i][j]过程中每个乘法结果的叠加值
loop4: dsll r8,r17,6 #这个循环的执行计算每个result[i][j]
dsll r9,r19,3
dadd r8,r8,r9
dadd r8,r8,r22
ld r10,0(r8) #取mx1[i][k]的值
dsll r8,r19,6
dsll r9,r18,3
dadd r8,r8,r9
dadd r8,r8,r23
ld r11,0(r8) #取mx2[k][j]的值
dmul r13,r10,r11 #mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 #中间结果累加
daddi r19,r19,1
slt r8,r19,r16
bne r8,r0,loop4
dsll r8,r17,6
dsll r9,r18,3
dadd r8,r8,r9
dadd r8,r8,r21 #计算result[i][j]的位置
sd r20,0(r8) #将结果存入result[i][j]中
daddi r18,r18,1
slt r8,r18,r16
bne r8,r0,loop3
daddi r17,r17,1
slt r8,r17,r16
bne r8,r0,loop2
halt
BTB機能を設定せず、プログラムを実行し、統計ウィンドウの結果を観察し、スクリーンショットを記録します。
次に、BTB機能を設定します(メニューバーの[構成]項目を選択し、ドロップダウンメニューの[分岐ターゲットバッファーを有効にする]オプションにチェックマークを付けます)。そして、ここでプログラムを実行し、統計ウィンドウで結果を観察し、スクリーンショットを撮って記録します。
次に、結果を比較します。この状況の原因をパイプラインと組み合わせて詳細に分析します。
注:初期化中に64サイクルのループを実行することを忘れないでください
BTBなしの分析
BTBがオンになっていない場合、mipsはデフォルトでジャンプしないと予測するため、最後のループが終了した場合にのみ、ループラベルにジャンプしません。つまり、予測は正しいです。したがって、初期化フェーズでは、64-1 = 63の分岐が発生します。
そして8*8*8
、それぞれのサイクルは、分岐予測の最後のステップであり、ジャンプがないこと、次に8回のサイクルであり、7つの分岐がストールします。
最も内側のループは、第二のループが8回実行され、最も外側のループは一度実行され、64回実行され、そこされる:64*7+8*7+1*7 = 511
分岐取らストール
BTB分析をオンにする
BTBをオンにすると、分岐したストールが148に減少しました。
初期化中の64の単層サイクルのうち、合計1つの分岐取得ストールと1つの分岐誤発音ストール、つまり最初と最後のジャンプが発生するため、それぞれ2サイクルを占めます。
38*8*8
ループ、合計73の分岐誤発音ストールと73分岐取得ストールは、合わせて146サイクルを占めます。それらの中で73=64+8+1
。以下のように分析します。
最も内側のループが終了するたびに、発音が誤って(前の予測はジャンプしたが、最後の予測はジャンプしなかったため)、BTB内の対応するアイテムが削除されます。
次に、次のサイクルが開始されます。BTBに対応するアイテムがないため、分岐したストールが発生します。最も内側のループは64回実行されるため、64の分岐の誤発音ストールと64の分岐取得ストールがあります。
第2レベルのループは8回実行され、最も外側のループは1回実行されますが、同じことが当てはまります。したがって、最終的には、分岐の誤発音のストールと分岐の取得のストール64+8+1=73
の数は両方ともであり、それらが占めるサイクルの数は次のようになります。73*2=146
したがって、最終的には、2+146=148
分岐の誤発音のストールサイクルと分岐が発生したストールがあります
パート2BTBを無効にする設計コード
このパートでは、ループを含むコードを設計します。BTBの特性によると、私たちが設計したコードでは、BTBを開くと効果を最適化できなくなりますが、パフォーマンスは大幅に低下します。
ヒント:必ずBTBの特性を使用してください。つまり、ジャンプの判断は、前のジャンプの成功または失敗に基づいて行われます。使用したコードと設計アイデアを示し、実行結果のスクリーンショットを示して、コードが目標を達成したことを証明します。
ループを設計するだけで、毎回異なるジャンプ結果が得られます。たとえば、ループカウンターiが奇数か偶数かに応じてジャンプします。これは、iが常に奇数であり、交互であるためです。
.data
.text
dadd r8, r0, r0
daddi r8, r8, 10 # r8 = i = 10
daddi r9, r0, 1 # r9 = 1
loop:
beq r8, r0, end # 跳出循环
and r10, r8, r9 # r10 = r8 & 1 取最低位判断奇偶
daddi r8, r8, -1 # i--
beq r10, r0, loop # 为偶数时跳转
j loop # 返回
end:
halt
i = 10を例にとると、偶数ジャンプがトリガーされます。このとき、BTBに記録される結果は「ジャンプ」です。i–、i = 9の場合、偶数ジャンプは失敗し、BTBは無効になります。i–、i = 8の場合、偶数ジャンプが再びトリガーされますが、この時点でBTBがクリアされているため、分岐が再び停止します。
BTBをオフにすると、10サイクルで11のブランチテイクストールが発生したことがわかりました(最後のbeqが飛び出したのも1回としてカウントされ、各サイクルjまたはbeqがエラーを予測したため、合計10エラー)
奇数と偶数の交代のため、奇数に遭遇するたびに、beq予測にバイアスがかかり、5つの分岐の誤発音ストールと分岐の取得ストール、合計10サイクルにつながることがわかります。
さらに、beqの開始と終了のループがあるため、取得したストールを2回以上10+4=14
分岐します。その結果、分岐サイクルで取得したストールが発生します。
行列の乗算を最適化するためのパート3ループ展開
まず、ループ展開の概念をある程度理解する必要があります。
ループ展開とは何ですか?いわゆるループ展開は、各反復でより多くのデータ操作を実行することにより、ループオーバーヘッドの影響を減らすことです。基本的な考え方は、操作オブジェクトを線形化し、線形データの小さなグループに1回ではなく1回の反復でアクセスすることです。結果のプログラムは実行する反復が少なくなるため、ループのオーバーヘッドが効果的に削減されます。
次に、この考えに従って、上記の行列乗算プログラムをループで展開します。ループ展開で上記のコードを展開して、8回の反復を実行する最も内側のループを展開する必要があります。つまり、コードを増やすことで、行列乗算の3つのループを2つのループに減らします。
ループ展開(BTBを有効にしない)、BTBを使用する(ループ展開なし)、BTBなし、ループ展開なしを比較して、結果を比較します。彼らのブランチタンケンストールとブランチ誤予測ストールの数を比較して、判断を下してみてください。
最も内側のループは最も時間がかかるため、最も内側のループの8つの累積を分割します。つまり、手動で8回書き込みmx1[i][k]*mx2[k][j]
、kを列挙して0〜7にします。
.data
str: .asciiz "the data of matrix 3:\n"
mx1: .space 512
mx2: .space 512
mx3: .space 512
.text
initial: daddi r22,r0,mx1 #这个initial模块是给三个矩阵赋初值
daddi r23,r0,mx2
daddi r21,r0,mx3
input: daddi r9,r0,64
daddi r8,r0,0
loop1: dsll r11,r8,3
dadd r10,r11,r22
dadd r11,r11,r23
daddi r12,r0,2
daddi r13,r0,3
sd r12,0(r10)
sd r13,0(r11)
daddi r8,r8,1
slt r10,r8,r9
bne r10,r0,loop1
# i=r17, j=r18, k=r19
mul: daddi r16,r0,8
daddi r17,r0,0
loop2: daddi r18,r0,0 #这个循环是执行for(int i = 0, i < 8; i++)的内容
loop3: daddi r19,r0,0 #这个循环是执行for(int j = 0, j < 8; j++)的内容
daddi r20,r0,0 #r20存储在计算result[i][j]过程中每个乘法结果的叠加值
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6 # r8 = i*64
dsll r9,r19,3 # r9 = k*8
dadd r8,r8,r9
dadd r8,r8,r22 # r8 = mx1[i][k]
ld r10,0(r8) # 取mx1[i][k]的值
dsll r8,r19,6 # r8 = k*64
dsll r9,r18,3 # r9 = j*8
dadd r8,r8,r9
dadd r8,r8,r23 # r8 = mx2[k][j]
ld r11,0(r8) # 取mx2[k][j]的值
dmul r13,r10,r11 # mx1[i][k]与mx2[k][j]相乘
dadd r20,r20,r13 # 中间结果累加
daddi r19,r19,1 # k++
# ----------------------------------------------------#
dsll r8,r17,6
dsll r9,r18,3
dadd r8,r8,r9
dadd r8,r8,r21 #计算result[i][j]的位置
sd r20,0(r8) #将结果存入result[i][j]中
daddi r18,r18,1
slt r8,r18,r16
bne r8,r0,loop3
daddi r17,r17,1
slt r8,r17,r16
bne r8,r0,loop2
halt
最も内側のループを分割するため、分析は次のようになります。
BTBがオンになっていない
したがって、オープンBTBがない場合、サイクルを初期化するのは、ブランチテイクストールの63回、外側ループが1回実行され、ブランチテイクストールが7回、内側ループが8回実行され、8*7
ブランチテイクストールが合計回です。8*7+1*7+63=126
倍の枝取り屋台
BTBをオンにする
BTBをオンにすると、初期化ループには64個の単層ループがあり、合計1つの分岐取得ストールと1分岐誤発音ストール、つまり最初と最後のジャンプが発生するため、それぞれ2サイクルを占有します。
また、ループが分割されているため、内層と外層には8つのループしかありません。内側のループは8回実行され、合計8つの分岐取得ストールと分岐誤発音ストール、外側ループは1回実行され、合計1つの分岐取得ストールと1つの分岐誤発音ストールになるため、最終的な数は9回になります。合計18サイクル。
包括的な初期化(2)とループ(18)、合計20期間の分岐取得ストールと分岐誤発音ストール
結びの言葉
分岐ステートメントを使用しないと、分岐予測の問題は発生しません。古典的なダチョウの考え方
分岐予測の計算には注意が必要です。BTBを使用しない場合、mipsは常にジャンプがないと予測するため、n回のサイクルで、合計n-1回の分岐が発生します。
BTBをオンにすると、ループが最初に開始されたときに分岐取得ストールと分岐誤発音ストールが1回発生し、ループが最後に終了したときに分岐誤発音ストールが発生します。ただし、BTBキャンセル操作には2サイクルが必要です。したがって、最終結果にはx2が必要です。
結論として
初期化ステートメントによって引き起こされるブロッキングを考慮して、分岐予測のブロッキング計算には注意が必要です。
BTBをオンにした後、元に戻す操作と記録操作には2サイクルかかるため、最終的なブロックには2を掛ける必要があり、計算エラーが発生しやすくなります。。。
ループを分割することは、分岐を回避するための優れた戦略です。キャッシュがタイトな一部のアプリケーションシナリオでは、非常に簡単に使用できます。