卒業研究 進捗発表 lightweight implementation of profile-driven implicit parallelization for...
DESCRIPTION
卒業研究 進捗発表 Lightweight Implementation of Profile-Driven Implicit Parallelization for Haskell. H22-12-22 理学部情報科学科 4 年 新井淳也. 概要 : Haskell で自動並列化. 2 段階かけて効率的な自動並列化を行う プロファイリングによって並列化による性能向上が見込める箇所を分析する 実際にそこを並列化する 自動並列化コンパイラを GHC ベースで実装中 GHC に既存の 機能を活用し簡易な実装を行 う. 背景 : 「 で、どこを 並列化すればいいの? 」. - PowerPoint PPT PresentationTRANSCRIPT
卒業研究進捗発表
Lightweight Implementation ofProfile-Driven
Implicit Parallelizationfor Haskell
H22-12-22理学部情報科学科 4 年 新井淳也
概要 :Haskell で自動並列
化• 2 段階かけて効率的な自動並列化を行う
1. プロファイリングによって並列化による性能向上が見込める箇所を分析する
2. 実際にそこを並列化する
• 自動並列化コンパイラを GHC ベースで実装中• GHC に既存の機能を活用し簡易な実装を行う
背景 :「で、どこを
並列化すればいいの?」• 「どうやって並列化するか」はよく研究されてきてい
る
• しかし並列化には「並列化すべき箇所の発見」と「そこを並列化したプログラムの実装」の 2 段階がある– 後者は楽になりつつあるが前者は手作業任せの研究が多かった
• 自力で探してね型– Cilk, StackThreads, Concurrent ML, Eden, Concurrent Haskell,
Glasgow Parallel Haskell, …• 自動で手当たり次第並列化型 ( 特殊なアーキテクチャを対象とす
る )– Id/pH, Guarded Horn Clauses, …
プロファイリングで「どこ」を発見できるはず• 並列計算にはオーバーヘッドがある
– 闇雲な並列化はプロセッサ間通信を増加させ逆に低速化を招く
• ある程度時間のかかる処理の塊を並列化するならオーバーヘッドがあっても高速化に貢献し得る
• それをプロファイリングによって発見する– データの依存関係や I/O のため並列化できない箇所もあるが、
それは静的なコード解析で対処できるはず
• なのでこの自動並列化はプロファイリングと再コンパイルの 2 段階を要する
この発想に基づくツールを
GHC を基に簡潔に実装する手法を提案
• Haskell は純粋関数型言語であり並列化が容易
• Glasgow Haskell Compiler は既にプロファイリングと並列化のための機能を備えている
• それらを活用し再発明のない簡潔な実装を与える– 過去の研究は既存のツールや実行時ライブラリに大幅な変更を
加えるものが多かった– 我々が独自手法の実装に専念できるということが主な利点
MethodAbstract と Introduction
Haskell は遅延評価
• Haskell は遅延評価を行う言語– 値は必要になるまで計算されない– 「サンク (thunk) 」に包み値が要求されるまで評価を遅らせる
• 引数を取らないクロージャのようなもの
• サンクの評価が一度行われると結果の値を記録し、次回以降はそれを返す– 基本的に評価は 1 回だけ
GHC での
プロファイリングは SCC プラグマの挿入で行う
• {-# SCC “name” #-} <expr>をソースに挿入してビルドすることで <expr> の評価回数や処理時間などを計測する実行ファイルが生成される– 実行時オプションを与えることで結果をファイルに出力させる
ことができる– SCC = Set Cost Centre
• 例let a = {-# SCC “fib-30” #-} fib 30 b = {-# SCC “fib-31” #-} fib 31in ...
GHC での
並列化は par 関数の挿入で行う
• par :: a -> b -> bは Glasgow Parallel Haskell (Trinder, et al. 1998)で実装された関数 現在は GHC に取り込まれている
• 第 1 引数を Spark させ第 2 引数はそのまま返す– Spark: ( ここでは ) サンクの評価を並行に行わせること– Spark したサンクはプールに入れられ手の空いたプロセッサに
より評価される
今回実装する機能の中核 :時間のかかる処理を
自動的に Spark させる• 手作業で並列化するときと考えることは同じ
– SCC を挿入して、時間はかかるが結果をすぐには使わない処理をプロファイルの分析で探し出し、 par 関数を挿入してSpark
– これが自動化されればめでたく自動並列化!
• 「時間がかかる」とは Spark のオーバーヘッドを相殺できそうな程度の時間をいう– 具体的な数値は実地でベンチマーク結果から決定
時間計測と Spark はサンクごとに
• 普通に Haskell のソースをコンパイルすればサンクを生成するようなコードを生成する
• このサンクごとに処理を行うのが最も自然で簡易– Spark はサンクを対象として行うもの– SCC による時間計測も内部的にはサンク単位– 積極的にサンクの生成箇所を制御する手もあるが、基本的にサ
ンクの生成はコストが大きいので少ないほうが良い
• ではサンクになる箇所を知るにはどうするか?
ImplementationMethod
GHC のコンパイルパイプライン
Parse•Haskellの抽象構文木へ
Rename•シンボルに一意な名前を与える
Typecheck•型検査
Desugar•Core(中間言語)へ
Simplify•最適化
CoreTidy•インターフェイス(.hi)生成
CorePrep•STG(中間言語)への変換準備
Convert to STG•STGへ変換
Code generation•C or バイナリへ
GHC の中間言語
CoreSystem F の一種・ Haskell のサブセット
• コンパイルオプションにより処理過程のコードをダンプ可能
• Core を読み込んでコンパイルすることは ( 今のところ ) できない
Rec {fib_rdj :: GHC.Types.Int -> GHC.Types.IntGblId[Arity 1]fib_rdj = \ (ds_siE :: GHC.Types.Int) -> case ds_siE of wild_siJ { GHC.Types.I# ds1_siH -> case ds1_siH of _ { __DEFAULT -> let { sat_siU :: GHC.Types.Int LclId [] sat_siU =
SCC や par の表現はCore でもほぼ同じ
• par# は par がインライン化されたもの– par# :: a -> Int#
第 1 引数を Spark し 1 を返す
GHC.Num.+ @ GHC.Integer.Type.Integer GHC.Num.$fNumInteger (__scc {fibzm30 main:Main} fib1_rlb (GHC.Integer.smallInteger 30)) (__scc {fibzm31 main:Main} fib2_rld (GHC.Integer.smallInteger 31))
Main.main2 = case GHC.Prim.par# @ GHC.Integer.Type.Integer Main.main_a of _ { __DEFAULT -> ...
サンク生成箇所はCorePrep で判明
• この時点の Core で let により束縛されているものが将来的にサンクになる– 次の中間言語、 STG において引数 0 のクロージャがサンクと
なる (The GHC Commentary より )– “Convert to STG” パスのソースを読み Core と STG のクロー
ジャ生成の関係を調査し確認した• 1 つ例外があるが単純な条件なので判別可能
Core でのコード変換で SCC や par を挿入
• GHC に既存の機能のお陰でこんなにも簡単– 現時点ではプロファイルの分析は実装できていないので、サンクを全
て Spark させてしまう
• この変換処理を“ CorePrep” の直後に挟んだ
let <binder> = <body>in <expr>
let <binder> = <body> incase GHC.Conc.par# <binder>of _ { __DEFAULT -> <body>
let <binder> = __scc <body>in <expr>
GHC のコンパイルパイプライン
改造 (1)Parse•Haskellの抽象構文木へ
Rename•シンボルに一意な名前を与える
Typecheck•型検査
Desugar•Core(中間言語)へ
Simplify•最適化
CoreTidy•インターフェイス(.hi)生成
CorePrep•STG(中間言語)への変換準備
Parallelize•SCC/parの挿入
Convert to STG•STGへ変換
Code generation•C or バイナリへ
let sat_siR = let sat_siS = let sat_siT = GHC.Types.I# 2 in GHC.Num.- @ GHC.Types.Int GHC.Num.$fNumInt wild_siG sat_siT in fib_rdf sat_siSin...
let sat_siR = let sat_siS = let sat_siT = GHC.Types.I# 2 in case GHC.Prim.par# @ GHC.Types.Int sat_siT of sat_sj8 { __DEFAULT -> GHC.Num.- @ GHC.Types.Int GHC.Num.$fNumInt wild_siG sat_siT } in case GHC.Prim.par# @ GHC.Types.Int sat_siS of sat_sj9 { __DEFAULT -> fib_rdf sat_siS } incase GHC.Prim.par# @ GHC.Types.Int sat_siRof sat_sja { __DEFAULT -> ...
変換
最適化でサンクが減る=並列性減少のジレンマ
• 並列化は当然高速化のためにやるのであって、コンパイラの最適化は有効にしたい
• しかしサンク生成は本来コスト源なので最適化でサンクの数は削られる
• サンクが作られなければ Spark する機会が減る– 例えば ( 効率は良くないが ) fib n = fib (n–1) + fib (n–2) と書いた時、最適化無しなら fib (n-1) と fib (n-2) でサンクが作られるが、最適化有りだと作られない
解決法 1:サンク数を減らす最適化
だけ無効にする• 最適化処理の中身をよく調べれば分かる…?
• 正格性解析が内容的にそれらしい雰囲気しかしコンパイル時に -O -fno-strictness を指定してもサンク数は減った
• プロファイル解析の結果 Spark することにしたサンク以外はなくなってくれたほうが良いので、そもそもこの方法は好ましくない。却下
解決法 2:変換を施した後
もう一度最適化をかける• SCC や par があるとき GHC はそこをサンクにする
• なのでそれらが挿入された状態の Core を Simplify させれば並列性を維持しつつ適用可能な最適化が行われる?
• しかし CorePrep まで進まないとどこがサンクになるかは分からない
Simplify の前にCorePrep までをゴッソ
リ挿入してみたが…
• 最適化オプションを無効にした状態で Simplify~ CorePrep を走らせられればサンクが多いままの Core を入手できる
• ならば Simplify の前に全部入れてしまおう– コンパイラの内部パラメタを弄ってこの間だけ最適
化を無効にする
GHC のコンパイルパイプライン
改造 (2)Parse Rename Typecheck
Desugar Simplify’(最適化Lv.0)
CoreTidy’ CorePrep’ Parallelize
Simplify CoreTidy
CorePrep Convert to STG
Code generation
問題点 1:最適化オプションの影響
を除けなかった• コンパイルオプション – O 無しでも有りでも
Parallelize パスへの入力は同じ…にできなかった– 並列化には -O を指定しなかった時の CorePrep の出力が欲し
い– ここで得られた出力は既にサンクが減少したものだった
• 最適化の設定は予想より複雑で、 Simplifier’直前でちょっと弄っても駄目– コンパイルオプションを格納するレコードがあり、最適化レベ
ルは整数でそこに記録されている– 内部では最適化レベルに応じて最適化フラグを入 /切している
問題点 1(続き ):… なので何を調べるか
• 後に Desugar でも最適化オプションが影響すると判明
• パイプライン突入直前で強制的に最適化レベルを 0 に書き換えておくと Parallelへの入力は同じになった– もしかすると Parse 前ではなく Desugar 前などでも大丈夫か
も?
• つまり最適化オプションが影響するより前の時点で介入して書き換えてしまえばよい– どこで書き換えるのがよいかについて調査中
問題点 2:コンパイル時に
panic• -O と -spark-thunks を同時に指定するとコ
ンパイル時に panic! -spark-thunks: par の挿入を指示する ( 自作 )
• 直接の原因 :Simplifier内の関数名置換処理に漏れが発生存在しない関数を参照するコードが発生– Main.fib を Main.$wfib に途中で置換する
が、 Main.fib の呼び出し箇所が Main.$wfib に変わらない
問題点 2(続き ):考えられる原因
1. “ 問題点 1”派生– 最適化の有効無効を切り替える中で必要な情報が欠落したのではないか• Parse…Desugar : この範囲は – O• Simplify’…Parallelize : この範囲は – O 無し• Simplify…Code generation : この範囲は – O
2. その他– 最終的にはその処理を行うコードを読むしか