コンパイラ演習 第 8 回 (2011/12/01)
DESCRIPTION
コンパイラ演習 第 8 回 (2011/12/01). 中村 晃一 野瀬 貴史 前田 俊行 秋山 茂樹 池尻 拓朗 鈴木 友博 渡邊 裕貴 潮田 資秀 小酒井 隆広 山下 諒蔵 佐藤 春旗 大山 恵弘 佐藤 秀明 住井 英二郎. 今回の内容. データフロー解析とそれに基づく最適化手法. マシン依存性による最適化の分類. マシン 非依存 静的に評価出来る部分をコンパイル時に計算 無駄な評価・計算を減らす マシン依存 パイプライン効率を上げる キャッシュ効率を上げる 両者に関係 並列度を上げる オーバヘッド除去. 局所性による最適化の分類. - PowerPoint PPT PresentationTRANSCRIPT
コンパイラ演習第 8 回
(2011/12/01)
中村 晃一 野瀬 貴史 前田 俊行秋山 茂樹 池尻 拓朗鈴木 友博 渡邊 裕貴潮田 資秀小酒井 隆広山下 諒蔵 佐藤 春旗大山 恵弘 佐藤 秀明住井 英二郎
今回の内容
• データフロー解析とそれに基づく最適化手法
マシン依存性による最適化の分類
• マシン非依存– 静的に評価出来る部分をコンパイル時に計算– 無駄な評価・計算を減らす
• マシン依存– パイプライン効率を上げる– キャッシュ効率を上げる
• 両者に関係– 並列度を上げる– オーバヘッド除去
局所性による最適化の分類
• 局所的最適化– Peephole 最適化– 基本ブロック内最適化
• 大域的最適化– 基本ブロック間最適化– 関数間最適化– プログラム間最適化
今回のターゲット
– マシン非依存– 基本ブロック間
• の– データフロー解析法– 最適化手法
• を扱います。
今回説明する基本ブロック間最適化
1. コピー伝播2. 定数畳みこみ3. 共通部分式除去4. 値番号付け5. 部分冗長性除去• 今回は到達定義解析・定義使用解析
に基づくアルゴリズムを紹介します。
基本ブロック間最適化の手順
1. 制御フローグラフを構築2. データフロー解析をする3. 最適化を行う
• 以上を個々の関数毎に行う
基本ブロックとは
• 入ってから出るまで一直線なプログラム片1.途中での合流が無く、2.途中からの脱出が無い
• 途中に関数呼び出しを含むブロックを許すか否かは最適化の種類による
let y = - x in…
let x = f() inif x > 0
let y = x in…
then
else
基本ブロック
準備:ループの検出
• MinCaml には明示的なループが存在しない→ そのままでは関数間解析をしないと 高度な最適化が難しい。
• 単純な方法– 自己末尾再帰呼び出しを while 型ループへ
let rec f n = if n < 10 then (…; f (n+1)) else ()in f 0
n = 0
if n < 10
…n = n + 1
準備:代入構文の用意
• ループ作成によって必要になる変数の更新を表現
• 左の様な let 文も右のように変換
n = 0
if n < 10
…n n + 1
let x = if ... then e1 else e2
if ...
x e1 x e2
let rec f n = if n < 10 then (…; f (n+1)) else ()in f 0
変数の更新
変数の更新
制御フローグラフ
• 関数 body を基本ブロックを頂点、制御移行を辺としてグラフを作る
• 空の entry,exit ブロックを用意– 入口・出口を用意しておくと
あとあと楽
entry
exit
データフロー解析 ( 前方解析 )• プログラムポイントの集合を P 、データの集合を D として以下を定義
– In[p] : D ( ポイント p の入口でのデータ )– Out[p]: D ( ポイント p の出口でのデータ )– pred: P2P ( ポイント p の一つ前のポイントの集合を得る
関数 )– f p: DD ( ポイント p における伝達関数 )
• すべてのポイント p について In[p], Out[p] を求める
p3
p1 p2
Out[p2]Out[p1]
In[p3]
データフロー解析 ( 後方解析 )• プログラムポイントの集合を P 、データの集合を D として以下を定義
– In[p] : D ( ポイント p の入口でのデータ )– Out[p]: D ( ポイント p の出口でのデータ )– succ: P2P ( ポイント p の一つ後のポイントの集合を得る
関数 )– f p: DD ( ポイント p における伝達関数 )
• すべてのポイント p について In[p], Out[p] を求める
p1
p2 p3
In[p2] In[p3]
Out[p1]
基本となるデータフロー解析
• 到達定義解析– ある場所で使用される物 ( 変数、式など ) が
どこで定義されたか ? ⇒ 前方解析
• 定義使用解析– ある場所で定義された変数が
どこで使用されるか?⇒ 後方解析
一方から容易に他方を求める事が出来る。今回は到達定義解析の結果から定義使用解析を行う
到達定義解析
• データフロー解析の一種• 使用される変数や式が
どこで定義されているかを調べる
• 流れ– まずプログラムポイントを基本ブロックとして解
析– 続いて基本ブロック毎に、
プログラムポイントをその中のステートメントとして解析
ブロック単位の到達定義解析:reach 集合
• reach[B]: 基本ブロック B の入り口に到達する定義文の集合
– これを計算する事が解析の目標
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3
reach[B1] = Φreach[B2] = {s1, s2, s5, s6, s7}reach[B3] = {s1, s2, s5, s6, s7}
ブロック単位の到達定義解析:def 集合
• def[B]: 基本ブロック B の中の定義文でB の出口で有効なものの集合
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3
def[B1] = {s1, s2}def[B2] = Φdef[B3] = {s5, s6, s7}
ブロック単位の到達定義解析:kill 集合
• kill[B]: B の出口で無効化される定義文の集合– 制御フローは考慮しなくてよい
B 内の文によって無効化され得る全ての定義文を集める。
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3
kill[B1] = {s6, s7}kill[B2] = Φkill[B3] = {s1, s2}
到達定義解析のデータフロー方程式
• とすれば以下を得る。
B
X
fB(X)
到達定義解析のアルゴリズム
全ての基本ブロック B について def[B] と kill[B] を計算し、 reach[B] := Φ とするreach[entry] := {arguments, global variables, ・・・ }do { old_reach := reach # 集合としてコピー 全ての基本ブロック B について { 全ての基本ブロック b ∈ pred(B) について { reach[B] := reach[B] ∪ def[b] ∪ (reach[b] \ kill[b]) } }} while (old_reach != reach) # 集合として比較
到達定義解析の具体例
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3kill[B1] = {s6, s7}kill[B2] = Φkill[B3] = {s1, s2}
def[B1] = {s1, s2}def[B2] = Φdef[B3] = {s5, s6, s7}
reach[B1] = Φreach[B2] = Φreach[B3] = Φ
※ここでは entry, exit を省略している
到達定義解析の具体例
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3kill[B1] = {s6, s7}kill[B2] = Φkill[B3] = {s1, s2}
def[B1] = {s1, s2}def[B2] = Φdef[B3] = {s5, s6, s7}
reach[B1] = Φreach[B2] = {s1,s2}reach[B3] = Φ
B1 pred(B2) ∈ の def
※ここでは entry, exit を省略している
到達定義解析の具体例
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3kill[B1] = {s6, s7}kill[B2] = Φkill[B3] = {s1, s2}
def[B1] = {s1, s2}def[B2] = Φdef[B3] = {s5, s6, s7}
reach[B1] = Φreach[B2] = {s1,s2,s5,s6,s7}reach[B3] = Φ
B3 pred(B2) ∈ の def
※ここでは entry, exit を省略している
到達定義解析の具体例
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3kill[B1] = {s6, s7}kill[B2] = Φkill[B3] = {s1, s2}
def[B1] = {s1, s2}def[B2] = Φdef[B3] = {s5, s6, s7}
reach[B1] = Φreach[B2] = {s1,s2,s5,s6,s7}reach[B3] = {s1,s2,s5,s6,s7}
B2 pred(B3) ∈ のreach\ kill
※ここでは entry, exit を省略している
到達定義解析の具体例
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
B1
B2
B3kill[B1] = {s6, s7}kill[B2] = Φkill[B3] = {s1, s2}
def[B1] = {s1, s2}def[B2] = Φdef[B3] = {s5, s6, s7}
reach[B1] = Φreach[B2] = {s1,s2,s5,s6,s7}reach[B3] = {s1,s2,s5,s6,s7}
変化なし変化なし
※ここでは entry, exit を省略している
ステートメント単位の到達定義解析
• 変数の到達定義解析の場合以下を計算– reach[s, x] : ステートメント s で使用されている変
数 x を定義している文集合
• s をブロック B の文とすると、 reach[s,x] は– B 内の s の先行命令が x を定義していれば
それのみ– そうでなければ、 reach[B] に含まれる x
を定義している文集合• として計算できる
ステートメント単位の到達定義解析の例
• 例えば reach[s6,i] についてみてみる– B3 の先行命令 (S4,s5) は I を定義しないので
reach[B] を見る。– reach[B] 内 で i を定義している文は
s1 と s6
–従ってs3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
reach[s6,i] = {s1, s6}
B1
B2
B3
reach[B3] = {s1,s2,s5,s6,s7}
• 最終結果
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
reach[s3, i] = {s1, s6}reach[s4, i] = {s1, s6}reach[s5,t] = {s4}reach[s6,i] = {s1, s6}reach[s7,s] = {s2, s7}reach[s7,t] = {s5}
B1
B2
B3
ステートメント単位の到達定義解析の例
定義使用解析
• 以下を計算する– use[s] : ステートメント s で定義されている変
数 x を使用している文集合
• 計算方法– use[s] = { t | s reach[t, x], x ∈ は s が定義する変
数 }
定義使用解析の例
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
use[s1] = {s3, s4, s6}use[s2] = {s7}use[s4] = {s5}use[s5] = {s7}use[s6] = {s3, s4, s6}use[s7] = {s7}
reach[s3, i] = {s1, s6}reach[s4, i] = {s1, s6}reach[s5,t] = {s4}reach[s6,i] = {s1, s6}reach[s7,s] = {s2, s7}reach[s7,t] = {s5}
実際の最適化の例 : コピー伝播
• 変数の到達定義解析・定義使用解析を利用copyprop(prog) {
worklist := (let x = v( 変数 ) の形の文 ) while (worklist != Φ) { s := worklist.pop foreach(t in use[s]) { x := s が定義する変数 if (|reach[t,x]| == 1) { # t に到達する変数が 1 つしかない t 内の x の使用を v に置換 worklist.add(t) if t が let x’ = v’ の形で worklist に存在しない } } }}
実際の最適化の例 : 定数畳みこみ
• 変数の到達定義解析・定義使用解析を利用constfold(prog) { worklist := 定数定義文 while (worklist != Φ) { s := worklist.pop foreach(t in use[s]) { x := s が定義する変数 x を定数値に置換 if reach[t,x] の全てが同じ値を定義 静的に t を評価 if t の全引数が定数 worklist.add(t) if t の計算結果が定数 } }}
実際の最適化の例 : 共通部分式除去
• 「利用可能な式」の到達定義解析を行う• ここでは基本ブロック単位の解析を説明する
– Eavail[B]: ブロック B の入口で利用可能な式集合– Ekill[B]: ブロック B 内でオペランドが
上書きされる式集合– Edef[B]: ブロック B 内にある式で
出口で利用可能な式集合• として以下を解く (∪ でなく∩となる点に注意 )
• その後ステートメント単位での解析をする
共通部分式除去の例
x = i * 4y = a + x
p = load yx = i * 4y = b + xq = load y
p = i * 4z = a + x
reach = {i * 4, a + x}
reach = {i * 4}
i * 4 が共通部分式になっている※右のパスで x が上書きされるので a + x はここに到達しない。
共通部分式除去の例
x = i * 4t = xy = a + x
p = load yx = i * 4y = b + xq = load y
p = i * 4z = a + x
reach = {i * 4, a + x}
reach = {i * 4}
i * 4 の為の一時変数を作って…
共通部分式除去の例
x = i * 4t = xy = a + x
p = load yx = ty = b + xq = load y
p = tz = a + x
reach = {i * 4, a + x}
reach = {i * 4} 共通部分式を除去
実際の最適化の例 : 値番号付け
• 先ほどのアルゴリズムでは例えば下の様なものが解決出来ない
a = b + cd = be = d + c
同じ値を持つが共通部分式じゃない
a = 1b = 1c = a + 2d = b + 2
同じ値を持つが共通部分式じゃない
実際の最適化の例 : 値番号付け
• 式の「値」を「番号」で識別して共通な値を見つける
a = 1b = 1c = a + 2d = b + 2
a = ①b = ①c = ② (②=①+2 )d = ②
a = b + cd = be = d + c
b = ①c = ②a = ③ (③ = + )① ②d = ①e = ③
実際の最適化の例 : 部分冗長性除去
• 値番号付けでも部分冗長なケースは解決できない
x = a + b
y = a + bこのパスでだけa + b が冗長
部分冗長性除去 : 簡単なアイデア
• 式を挿入して全冗長にする
z = a + bx = a + b
y = a + b
z = a + b
部分冗長性除去
• 手順– 共通部分式の除去– 危険辺の除去– 入口計算・出口計算・挿入点を検査– 上安全性・下安全性の検査– 挿入点を求める
• 今回はせわしいコード移動 (busy-code-motion) という方法を紹介– 式をできるだけ前方へ挿入する
• 以下 a + b という形の一つの式に注目する
危険辺の除去
• 2つ以上の後続ブロックを持つブロックから、2つ以上の先行ブロックを持つブロックへの辺を新たにブロックを挿入して除去
y = a + b y = a + b
ここに a + b を挿入すると後続のパスに影響
ここなら a + b を挿入しても大丈夫
入口計算・出口計算• ブロック B 内に a, b への代入文がある場合、
その最後の代入文の直後で分割– 入口計算 : 入口部分にあり、かつ a, b の代入より前にある a + b– 出口計算 : 出口部分にある a + b
• ブロックの集合を B として– NCompa+b: BBoolean ( ブロック B が入口計算を持つ )
– XCompa+b: BBoolean ( ブロック B が出口計算を持つ )
– Transpa+b: BBoolean ( ブロック B 内に a, b への代入文がない )
x = a + ba = 2b = x + by = a + b
出口計算
入口計算
x = a + ba = 2b = x + by = a + b
挿入点• 以下の 2 つのプログラムポイントを「挿入点」と呼ぶ
– 入口挿入点• 入口計算の直前 ( 入口計算がなければ基本ブロックの先頭 )
– 出口挿入点• 出口計算の直前 ( 出口計算がなければ基本ブロックの最後 )
x = a + ba = 2b = x + by = a + b
出口計算
入口計算
x = a + ba = 2b = x + by = a + b 出口挿入点
入口挿入点
上安全性・下安全性• ある挿入点に a + b を代入しても安全かどうか• 上安全性
– entry からその点までのどのパスにも a + b があり全部同じ値
• 下安全性– その点から exit まで
どのパスにも a + b があり全部同じ値
• NuSafea+b[B] : Boolean(B の入口挿入点で上安全 )
• XuSafea+b[B] : Boolean (B の出口挿入点で上安全 )
• NdSafea+b[B] : Boolean(B の入口挿入点で下安全 )
• XdSafea+b[B] : Boolean (B の出口挿入点で下安全 )
上安全性・下安全性の解析
• NdSafe, XdSafe は以下の方程式で計算できる
• NuSafe, XuSafe も同様
せわしいコード移動
• 下安全で出来るだけ前方の挿入点にt = a + b を挿入
• NEarlist[B] = true である B の入口挿入点 ,XEarliest[B] = true である B の出口挿入点にt = a + b を挿入
• すべての入口計算・出口計算を t に置換
その他最適化のヒント :Do-All 型ループの検出
• 後の回やる並列化・キャッシュ最適化等に欠かせない作業
• Do-All 型ループとは– i = a,a+d,a+2d,…,a+(k-1)d と順番に回すループ
• while ループには既に直してあるのだからインデックス変数 i を見つけてやればよい
for (i = a; i < n; i += d) { …}
Do-All 型ループの検出 :帰納的変数検出
• Do-All 型ループのインデックスは帰納的変数になっている– i0 = a– in+1 = in + d– ループ脱出条件により
i の上限 ( 下限 ) が決定出来る• 到達定義解析の結果から
右のようなパターンを見つければよい
i = a
i = i + d
if ループ脱出条件
帰納的変数の検出 (例 )
s3: if i < 100
s1: i = 0s2: s = 0
s4: t = is5: t = 2*ts6: i = i + 1s7: s = s + t
s = 0
for (i=0;i < 100; i++)
t = it = 2*ts = s + t
共通課題
• 二つの共通課題のうち一つ以上を解いてください
課題 1
• MinCaml に到達定義解析を実装しそれを用いた最適化 ( 最低 1 つ ) を実装せよ。– 第 2 回で扱った到達定義解析に寄らない最適
化と比較し評価せよ。
課題 2• 今回紹介した単純な部分冗長性除去では
上手く冗長性が除去出来ないプログラムや遅くなってしまうプログラムがある
• その例を挙げ、改善する方法を考えよ
– 参考文献• Knoop, J., Ruthing, O., and Steffen, B. Lazy Code Motion. ACM SIGPLAN
Notices Vol. 27, Num. 7, Jul. 1992, '92 Conference on PLDI.• VanDrunen, T., and Hosking, A.L. Value-Based Partial Redundancy Elimination,
Lecture Notes in Computer Science Vol. 2985/2004, pp. 167 - 184, 2004.• Kennedy, R., Chan, S., Liu, S.M., Lo, R., Peng, T., and Chow, F. Partial
Redundancy Elimination in SSA Form. ACM Transactions on Programming Languages Vol. 21, Num. 3, pp. 627-676, 1999.
コンパイラ係用課題
• 関数間解析・エイリアス解析などさらに高度な解析とそれを用いた最適化を実装せよ
課題の提出先と締め切り
• 提出先 : [email protected]• 締め切り : 2 週間後 (12/15) の午後 1 時
– コンパイラ係用課題の締め切り : 2012 年 2月 27日
• Subject: Report 8 <学籍番号 : 5 桁 >
–例: Report 8 11099• 本文にも氏名と学籍番号を明記のこと
半角スペース 1 個ずつ
質問は [email protected] まで