(57)【要約】  【課題】  JavaやPHPのような副作用のある命令型言語に遅延評価を適用して実行コードの性能を向上させること。【解決手段】  ソースコードの解析においてデータおよび副作用の依存グラフを生成し、その後支配木を元に遅延できるプログラムの部分を特定し、遅延を行うコードを生成し、実行時プロファイラによって、前記遅延がどこで強制されたかをプロファイルし、プロファイルによって高頻度で強制されると判定された遅延クロージャのコードをその強制される箇所にインラインすることで移動し、さらに部分評価を適用してコードを生成し、前記生成されたコードにおいて、中間データ構造を部分評価で除去するコンパイル技法を開示する。【選択図】  図３
2011065220.tif 000002
【特許請求の範囲】
【請求項１】
コンピュータの処理によって、ソースコードを、実行可能コードに変換するためのコンパイル方法であって、
前記コンピュータの処理によって、前記コンピュータの記憶装置に格納された前記ソースコードを読み込むステップと、
前記コンピュータの処理によって、前記ソースコードの解析においてデータおよび副作用の依存グラフを生成し、その後支配木を元に遅延できるプログラムの部分を特定するステップと、
前記コンピュータの処理によって、遅延を行うコードを生成するステップと、
前記コンピュータの処理によって、プロファイラの作用で、前記遅延がどこで強制されたかをプロファイルするステップと、
前記コンピュータの処理によって、前記プロファイルによって高頻度で強制されると判定された遅延クロージャのコードをその強制される箇所にインラインすることで移動し、さらに部分評価を適用してコードを生成するステップと、
前記コンピュータの処理によって、前記生成されたコードにおいて、中間データ構造を部分評価で除去するステップを有する、
コンパイル方法。
【請求項２】
前記遅延を行うコードを生成するステップが、データ構造の更新を遅延していいかどうかの判断がエイリアスの可能性のため難しい場合、データ構造内部のエイリアスの可能性を調べ、安全な場合のみに実行される、請求項１に記載のコンパイル方法。
【請求項３】
遅延を行うコードを生成するステップの前に、前記ソースコードをＳＳＡ変換するステップを有する、請求項１に記載のコンパイル方法。
【請求項４】
前記ソースコードがPHPで書かれている、請求項１に記載のコンパイル方法。
【請求項５】
コンピュータの処理によって、ソースコードを、実行可能コードに変換するためのコンパイラ・プログラムであって、
前記コンピュータをして、
前記コンピュータの記憶装置に格納された前記ソースコードを読み込むステップと、
前記ソースコードの解析においてデータおよび副作用の依存グラフを生成し、その後支配木を元に遅延できるプログラムの部分を特定するステップと、
遅延を行うコードを生成するステップと、
プロファイラの作用により、前記遅延がどこで強制されたかをプロファイルするステップと、
前記プロファイルによって高頻度で強制されると判定された遅延クロージャのコードをその強制される箇所にインラインすることで移動し、さらに部分評価を適用してコードを生成するステップと、
前記生成されたコードにおいて、中間データ構造を部分評価で除去するステップを実行させる、
コンパイラ・プログラム。
【請求項６】
前記遅延を行うコードを生成するステップが、データ構造の更新を遅延していいかどうかの判断がエイリアスの可能性のため難しい場合、データ構造内部のエイリアスの可能性を調べ、安全な場合のみに実行される、請求項５に記載のコンパイラ・プログラム。
【請求項７】
遅延を行うコードを生成するステップの前に、前記ソースコードをＳＳＡ変換するステップを有する、請求項５に記載のコンパイラ・プログラム。
【請求項８】
前記ソースコードがPHPで書かれている、請求項５に記載のコンパイラ・プログラム。
【請求項９】
ソースコードを、実行可能コードに変換する処理を行うコンピュータ・システムであって、
前記ソースコードを格納した記憶装置と、
主記憶と、
前記ソースコードを前記主記憶に読み込む手段と、
前記ソースコードの解析においてデータおよび副作用の依存グラフを生成し、その後支配木を元に遅延できるプログラムの部分を特定する手段と、
遅延を行うコードを生成する手段と、
プロファイラの作用により、前記遅延がどこで強制されたかをプロファイルする手段と、
前記プロファイルによって高頻度で強制されると判定された遅延クロージャのコードをその強制される箇所にインラインすることで移動し、さらに部分評価を適用してコードを生成する手段と、
前記生成されたコードにおいて、中間データ構造を部分評価で除去する手段を含む、
コンピュータ・システム。
【請求項１０】
前記遅延を行うコードを生成する手段が、データ構造の更新を遅延していいかどうかの判断がエイリアスの可能性のため難しい場合、データ構造内部のエイリアスの可能性を調べ、安全な場合のみに実行される、請求項９に記載のコンピュータ・システム。
【請求項１１】
遅延を行うコードを生成する手段に入力するために、前記ソースコードをＳＳＡ変換する手段をさらに有する、請求項９に記載のコンピュータ・システム。
【請求項１２】
前記ソースコードがPHPで書かれている、請求項９に記載のコンピュータ・システム。
【発明の詳細な説明】
【技術分野】
【０００１】
この発明は、コンパイラ技術に関し、特に、プログラムの実行時の性能を向上させるための技術に関する。
【背景技術】
【０００２】
近年PHP、Perl、Ruby、Javascript(R)などの動的スクリプティング言語が主流になってきている。これらの言語は静的な型を持たず、そのかわり動的に、また緩くモジュール間を結合する仕組みを特徴とする。例えば、PHPのアプリケーションではモジュール間のデータのやりとりがJava(R)のようなクラス/オブジェクトベースではなく、連想配列(ハッシュテーブル)を用いたものになっていることが多い。これは型ではなく名前でモジュール間のインタフェースを決めるということであり、このことは、アプリケーションの自由度を高くするが、同時にデータのやりとりのコストも高い。そのため、効果的なコンパイル時の最適化手法が大切になってきている。例えば、米国SugarCRM社のCRMソフトウェアである、PHPのSugarCRMでのベンチマークの結果では連想配列のランタイム処理が合計で約30%を占めていた。また、PHPではグローバル変数やオブジェクトのフィールドなど、ほぼ全てのものが連想配列で表現されている。
【０００３】
このような問題を解くのに、部分評価その他のデータ構造を除去するような最適化を使えるとよいが、そのためには、連想配列の生成サイトと使用サイトの間のデータフローが明らかになっている必要がある。しかし、大きなアプリケーションでは、連想配列の生成サイトと使用サイトが離れているため、その間の大域的なデータフローを、解析で得ることは難しい。また、動的なモジュール結合自体がデータフローをわかりにくくすることなどが問題をさらに難しくしている。
【０００４】
Java(R)のようなオブジェクト指向系言語も、より以前の言語に比べクラスベース抽象のコストと呼ばれる実行時コストがかかる。しかも、オブジェクト指向系言語の継承や仮想メソッドなどが、プログラムのデータフローを静的にわかりにくくするため、静的な最適化手法だけでこのようなコストを取り除くのは難しい。そこで、オブジェクト指向系言語の世界では今まで、多相インラインキャッシュやオブジェクト・プロファイリングなどの動的なフィードバック駆動の最適化技術が発展してきた。これらの技術のアイディアは、プログラムの大域的なデータフローを実行時のフィードバックに基づいて推測することで、最適化を可能にするというものである。例えば、オブジェクトのクラス情報の大域的なデータフローを推測すれば、推測されたクラスの仮想メソッドをインライン展開してその後続コードと組み合わせて最適化することができた。しかし、このフィードバック駆動の最適化技術を、動的スクリプティング言語の連想配列などの問題に適用するのは自明ではない。なぜなら、たとえ大域的なデータフローが推測できたとしても、その後で連想配列の生成サイトのコードと使用サイトのコードを組み合わせて最適化する手法が明らかでないためである。
【０００５】
別の背景として、JavaのWebSphere(R) Application ServerやPHPのSugarCRMのような大きなアプリケーションで遅延評価が効果的だという知見がある。特に、PHPのようなリクエスト毎にプログラムが一回走るようなステートレスな実行モデルでは、リクエスト毎にアプリケーションの初期化のロジックが走る。この初期化で生成されたデータは実はごく一部しか使われない可能性があり、遅延評価の効果が期待される。しかし、このような遅延評価を、JavaやPHPのような副作用のある命令型言語について、体系的にコンパイラ・レベルで行う手法はあまり実用化されてこなかった。原因のひとつには、遅延された評価が最後まで強制されなければコストの削減になるが、評価が強制された場合は遅延しない場合よりも高いコストがかかるということが考えられる。
【０００６】
特開平１０−１１２９９号公報は、前方参照を指示する命令文がどのような構文解析の要素であるかにかかわらず、該命令文を含むソースプログラムを構文解析し、効率良くコンパイルすることができるようにするために、ソースプログラムに含まれるトークン列からトークンを順次読み込むトークン選択読み込み部と、前記トークン選択読み込み部が読み込んだトークンが、予め前記トークン列の任意の区間に設定した遅延評価区間内のトークンである場合には、該トークンを遅延評価トークン記憶テーブルに記憶する遅延評価部分記憶部と、前記遅延評価部分記憶部により前記遅延評価トークン記憶テーブルに記憶されたトークンを順次読み込み、オブジェクト化してオブジェクトファイルへ出力する評価処理部とから構成されるコンパイル装置を開示する。
【０００７】
しかし、このようなコンパイラにおける遅延評価技法は、評価が強制された場合は遅延しない場合よりも高いコストがかかるという問題に効果的な解法を与えるものではない。
【先行技術文献】
【特許文献】
【０００８】
【特許文献１】特開平１０−１１２９９号公報
【発明の概要】
【発明が解決しようとする課題】
【０００９】
この発明の目的は、JavaやPHPのような副作用のある命令型言語に遅延評価を適用して実行コードの性能を向上させることが可能なコンパイラ技法を提供することにある。
【課題を解決するための手段】
【００１０】
本発明では、上記目的を達成するためになされたものであり、PHPなどの比較的高いデータ操作の実行時コストを持つ言語と連想配列などによる緩いモジュール間の結合をもつアプリケーションを対象に、その大きな実行時コストの問題を、フィードバックに基づく大域的コード移動という手法によって解決する。
【００１１】
この手法は、
１．高い実行時コストをもち、かつ安全に移動することが可能なデータの生成サイトのコード・フラグメントを解析によって判定し、この部分に対して評価を遅延するコードを生成する(レベル１コンパイル)。
２．ステップ１で生成された遅延が高頻度で強制される場所(データの使用サイト)を実行時フィードバックに基づいて推定し、この遅延をこの使用サイトのコードにインライン展開することでコード移動を実現し、部分評価などの強力な最適化を可能にする(レベル２コンパイル)。
という二つのコンパイルステップで実現される。
【００１２】
ステップ１の遅延生成は、一時的にクロージャ作成のための実行時コストを増やす。ただし、もし遅延されたコードが作る値が以降でまったく必要にならない場合は、通常の遅延評価の技術と同じようにそのコードの評価のコストが省かれ、これにより結果的に利得が得られる。すなわち、ある程度高いコストの処理を選択的に遅延することで、遅延生成自体のコストが遅延なしの評価のコストと比べると小さくなるようにすればよい。
【００１３】
本発明による特徴的な効果が得られるのは、ステップ１で作られた遅延が強制される場合である。この場合は、ステップ２が、大域的コード移動による最適化を行い新しい種類のコスト削減を試みる。ここで、慣用的なコンパイラ技術では、コード移動といえば通常はコンパイル単位内での局所的な移動だけであり、また、インライン展開も元から存在している関数やメソッドについてしか行うことができなかった。しかし、本発明のステップ１は、積極的に高いコストをもつ移動可能なコードを発見して遅延を生成する。ここで作られた遅延は、関数クロージャ(オブジェクト)と同じように扱えるので、プロファイリングによってそれが必要になりそうな使用サイトを推定し、そのコードにガードつきでインライン展開することができる。これによってコンパイル単位を越えた大域的なコード移動が実現される。
【００１４】
こうして、大域的なコード移動  (遅延コードのインライン展開)  を行うと、より強力な最適化の機会が得られる。例えば、連想配列の生成サイトのコードを移動した場合には部分評価の手法を使って、連想配列の生成およびストア/ロード操作を取り除くことができる。PHPでは、連想配列処理のコストが非常に高いので、この部分評価による利得が、多くのケースで遅延生成・クロージャ操作のコストを上回る.また本発明は、連想配列処理以外でも、後で発明を実施するための形態の節で説明するが、ファイルからの非常に長い文字列の生成などの他の高コストの処理に対しても適用することができる。一例として、文字列の使用サイトのコードが文字列のI/O出力であった場合、この処理をsendfileによるＤＭＡ処理(zero-copyデータ転送)に書き換える最適化で、文字列生成のコストを取り除くことができる。
【００１５】
例えば、以下のPHPのコードを例にとる。PHPでは、トップレベルで定義された変数はグローバル変数の扱いになることに注意する。
<?php 
$user = "akihiko";  $date = date(DATE_RFC822); 
start();
?> 
【００１６】
この start() からのアプリケーションのどこかで、login() が呼ばれるとする。
function login() { 
global $user, $date; 
echo "user $user logined at $date"; 
}
【００１７】
まずレベル１コンパイラは、コードの中の遅延可能な箇所を探し出し、これを遅延する。ここでは、コンパイラが以下のような関数型の中間言語 (A正規形) を作ることとするが、 遅延されたコードを表現することができるなら、SSAなど他の中間言語でもかまわない。 なお、A正規形については、C. Flanagan, A. Sabry, B. F. Duba, and M. 
Felleisen. The essence of compiling with continuations. In Proceedings of 
the ACM SIGPLAN ’93 Conference on Programming Language Design and
Implementation, pages 237-247, 1993. In ACM SIGPLAN Notices, 28(6), June 1993などを参照されたい。また、SSAとは、静的単一代入形式(Static Simple Assignment)のことであり、変数の定義が字面上唯一になるように添字をつけた中間表現形式であり、コンパイラにおけるデータフロー解析や最適化を見通しよく行うのに適した形式のことである。
let _0 = date(DATE_RFC822) in
let _ = delay_global(fun _ -> 
let _ = upd_global "user" "akihiko" in
upd_global "date" _0)
in 
start() 
【００１８】
ここで、delay_global操作は、global変数に対する更新処理を遅延するものであり、(fun _ -> …) で表現される遅延された処理は実行されることなく実行系に登録される。この遅延処理はクロージャc = (fn, record)で表現されていて、fnは関数の実体をあらわし、 遅延できなかった値たとえば _0は、クロージャレコードrecord中に捕獲されている。fnはコンパイル時定数となり、 一方 recordは、実行時に作られることに注意する。各クロージャは、インタプリタで処理される形でもコンパイルした形でもよいが、後の部分評価のために(fun _ -> …) という中間言語のコード断片が付随しているものとする。
【００１９】
実行時には login() の場所でglobal変数に対する読み込みを行う。
let login _ = 
let _0 = load_global "user" in
let _1 = load_global "date" in
echo ("user ". _0."logined at"._1)
もし、start()からlogin()までの間にglobal変数の定義がなければ、load_globalの実行時に先ほど遅延されたクロージャc = (fn,record) が実行系から取り出され処理される。実行時プロファイラは、クロージャcがこのlogin()の実行時に強制されたことをプロファイルしておく。
【００２０】
レベル2コンパイラはまず、プロファイル情報を元に遅延されたクロージャ中のコード fnをlogin() 関数の中にインラインする。このとき実際に実行されるコードfn’がfnと等しいかどうかのガードを同時に生成し、もしガードにヒットすれば、fnに相当するインラインしたコードが実行されるようにする。
【００２１】
let login_fast _ =
let (fn’, record) = delayed_global() in 
if (fn’ == fn) then 
let _ = upd_global "user" "akihiko" in 
let _ = upd_global "date" record#_0 in
let _0 = load_global "user" in
let _1 = load_global "date" in
echo("user" . _0 . "logined at" ._1)  
else login()
【００２２】
ただし、record#_0 はレコード中の_0フィールドからの読みだしを意味するとする。 最終的には部分評価器が、以下のようにコードを簡単化する。
let login_fast _ =
let (fn’, record) = delayed_global () in 
if (fn’ == fn) then
echo("user akihiko logined at" . record#_0) 
else login()
すなわち、定数畳み込みや中間データ構造除去の効果を大域的なデータフローの解析なしで得ることができるようになった。また遅延したグローバル変数テーブルの更新処理をこの場所では省くことができた。もし、このlogin()以降にもうグローバル変数の更新がないなら、グローバル変数テーブル生成のコストを完全に除去することができたことになる。 
【発明の効果】
【００２３】
この発明によれば、高い実行時コストをもち、かつ安全に移動することが可能なデータの生成サイトのコードフラグメントを解析によって判定し、この部分に対して評価を遅延するコードを生成するステップと、そのステップで生成された遅延が高頻度で強制される場所(データの使用サイト)を実行時フィードバックに基づいて推定し、この遅延をこの使用サイトのコードにインライン展開することでコード移動を実現するステップを実行することによって、JavaやPHPのような副作用のある命令型言語においても、部分評価などの強力な最適化を可能にするという効果が提供される。
【図面の簡単な説明】
【００２４】
【図１】本発明を実行するためのハードウェアのブロック図である。
【図２】本発明で使用される機能のブロック図である。
【図３】中間言語と、実行系及びプロファイル情報の関係を示す図である。
【図４】本発明に係るコンパイル処理のフローチャートを示す図である。
【図５】依存解析によって生成されるデータ依存グラフと、後支配木の例を示す図である。
【図６】副作用の種別を考えた場合の、データ依存グラフと、後支配木の例を示す図である。
【発明を実施するための形態】
【００２５】
以下、図面に基づき、この発明の実施例を説明する。特に断わらない限り、同一の参照番号は、図面を通して、同一の対象を指すものとする。尚、以下で説明するのは、本発明の一実施形態であり、この発明を、この実施例で説明する内容に限定する意図はないことを理解されたい。
【００２６】
図１を参照すると、本発明の一実施例に係るシステム構成及び処理を実現するためのコンピュータ・ハードウェアのブロック図が示されている。図１において、システム・バス１０２には、ＣＰＵ１０４と、主記憶（ＲＡＭ）１０６と、ハードディスク・ドライブ（ＨＤＤ）１０８と、キーボード１１０と、マウス１１２と、ディスプレイ１１４が接続されている。ＣＰＵ１０４は、これには限定されないが、好適には、３２ビットまたは６４ビットのアーキテクチャに基づくＣＰＵであり、例えば、インテル社のＰｅｎｔｉｕｍ（商標）  ４、Ｃｏｒｅ（商標）２  Ｄｕｏ、Ｃｏｒｅ（商標）２  Ｑｕａｄ、Ｘｅｏｎ（商標）、ＡＭＤ社のＡｔｈｌｏｎ（商標）、Ｔｕｒｉｏｎ（商標）などを使用することができる。主記憶１０６は、好適には、２ＧＢ以上の容量をもつものである。ハードディスク・ドライブ１０８は、例えば、３２０ＧＢ以上の容量をもつものである。
【００２７】
ハードディスク・ドライブ１０８には、個々に図示しないが、オペレーティング・システムが、予め格納されている。オペレーティング・システムは、Ｌｉｎｕｘ（商標）、マイクロソフト社のＷｉｎｄｏｗｓ  Ｖｉｓｔａ（商標）、Ｗｉｎｄｏｗｓ  ＸＰ（商標）、Ｗｉｎｄｏｗｓ（商標）２０００、アップルコンピュータのＭａｃ  ＯＳ（商標）などの、ＣＰＵ１０４に適合する任意のものでよい。
【００２８】
ハードディスク・ドライブ１０８にはまた、本発明に係るプログラム言語処理系その他のプログラムも格納される。これには限定されないが、この実施例では、プログラム言語として、PHPを想定する。
なお、PHPの仕様については、http://www.php.net/などを参照されたい。
【００２９】
ハードディスク・ドライブ１０８にはさらに、プログラム言語処理系でコンパイルするためのソースコードを書くためのテキスト・エディタ、及び、Ｅｃｌｉｐｓｅ（商標）などの開発環境を含んでいてもよい。
【００３０】
キーボード１１０及びマウス１１２は、オペレーティング・システムまたは、ハードディスク・ドライブ１０８から主記憶１０６にロードされ、ディスプレイ１１４に表示されたプログラム（図示しない）を起動したり、文字を打ち込んだりするために使用される。
【００３１】
ディスプレイ１１４は、好適には、液晶ディスプレイであり、例えば、ＸＧＡ（１０２４×７６８の解像度）、またはＵＸＧＡ（１６００×１２００の解像度）などの任意の解像度のものを使用することができる。ディスプレイ１１４は、本発明に係るコンパイラの処理結果やエラーなどを表示するために使用される。
【００３２】
図２は、本発明に係る機能の概要ブロック図である。図２において、ソース・プログラム２０２は、例えば、PHPで書かれたソース・プログラムであり、ハードディスク・ドライブ１０８に保存されている。
【００３３】
変換モジュール２０４は、ハードディスク・ドライブ１０８に保存され、オペレーティング・システムの働きによって主記憶１０６にロードされ、ソース・プログラム２０２をパースして、Ａ正規形変換、またはＳＳＡ変換を施し、中間言語を生成する機能を有する。生成された中間言語は、主記憶１０６に配置されるか、またはハードディスク・ドライブ１０８に保存される。
【００３４】
コンパイラ２０６は、本発明に従いコンパイル処理を行うものであり、ハードディスク・ドライブ１０８に保存され、オペレーティング・システムの働きによって主記憶１０６にロードされ、変換モジュール２０４によって生成された中間言語を、実行コードに変換する。特にコンパイラ２０６は、後で詳述するが、レベル１コンパイラと、レベル２コンパイラからなる。
【００３５】
コンパイラ２０６によって生成された実行コードは、好適にはハードディスク・ドライブ１０８に一旦保存され、オペレーティング・システムによって用意された実行系２０８において実行される。
【００３６】
コンパイラ２０６によって生成された実行コードが実行系２０８において実行されたときに、実行時プロファイラ（図示しない）が、プロファイル情報２１０を生成する。実行時プロファイラは、以下では単にプロファイラとも呼ぶが、コンパイラ２０６によって生成されるコードの機能の一部と考えることができる。プロファイル情報２１０は、好適にはハードディスク・ドライブ１０８に書き出されるが、主記憶１０６に配置してもよい。
【００３７】
本発明によれば、こうして生成されたプロファイル情報２１０は、コンパイラ２０６によって利用される。
【００３８】
図３は、生成された中間言語レベルのブロック図である。図３において、ソース・プログラム２０２は、変換モジュール２０４によってパース及びＳＳＡ変換され（または、ＡＡ正規形変換され）、レベル０中間言語３０２に変換される。
【００３９】
こうして生成されたレベル０中間言語３０２は、コンパイラ２０６のレベル１コンパイラによって、処理の遅延及びプロファイル操作を伴ったレベル１中間言語３０４が生成される。
【００４０】
レベル１中間言語３０４を実行コードに変換して実行系２０８で走らせると、遅延の強制に関するプロファイル情報２１０が収集される。
【００４１】
ある程度の回数のコードの実行の後に、高頻度で特定の遅延コードが強制されると分かる場所が見つかった場合、レベル２コンパイラが発動して、部分評価により、この箇所を含むコードを最適化し、より高速なレベル２中間言語３０６に置き換える。
【００４２】
図４は、コンパイラ２０６の処理ステップをより詳細に説明するためのフローチャートである。前述のように、コンパイラ２０６は、レベル１およびレベル２の２つの最適化レベルを持ち、実行時プロファイラを持つ実行系２０８と協働する。レベル１コンパイラは、逐次評価型の言語のコード解析を行い、 その中で評価が遅延できるコードフラグメントを特定し、この部分に対して遅延クロージャの生成を行う。
【００４３】
図４において、ステップ４０２では、レベル１コンパイラは、コードの解析においてデータおよび副作用の依存グラフを生成し、その後支配木を元に遅延できるプログラムの部分を特定する。
【００４４】
ステップ４０４では、レベル１コンパイラは、データ構造の更新を遅延していいかどうかの判断がエイリアスの可能性のため難しい場合、データ構造内部のエイリアスの可能性を調べ、安全な場合のみに遅延を行うようなコードを生成する。
【００４５】
ステップ４０６では、レベル１コンパイラが生成したコードが作る遅延クロージャは、実行時に必要な箇所で強制されるが、実行時プロファイラはこの遅延がどこで強制されたかをプロファイルする。
【００４６】
ステップ４０８では、レベル２コンパイラは、プロファイルによって高頻度で強制されると判定された遅延クロージャのコードをその強制される箇所にインラインすることで移動し、さらに部分評価を適用して高速なコードを生成する。
【００４７】
ステップ４１０では、レベル２コンパイラは、配列などのデータ構造の内部を陽に表現できるような中間言語を使うことで、中間データ構造を部分評価で除去する。 このコンパイル時データ構造の形は実行時データ構造のヒープ内での形と同じ必要はなく、データ構造への操作の意味だけを保存する。
次に、個々のステップの具体的処理について説明する。
【００４８】
＜レベル１コンパイラ＞
ここでは、 Ａ正規形やＣＰＳなどの各関数定義の実体 (制御グラフの基本ブロックに対応している) について、 データの依存解析をおこない、 遅延できる箇所を特定する方法を示す。例えば、以下のようなコードを考える。今回は $x, $y, $z などはローカル変数であるとし、最初は I/Oの遅延については考えないとする。
0:  let $x = 1 in 
1:  let $y = 2 in   
2:  let $z = $x + $y in
3:  let _ = echo $x in  (* 副作用 *)
4:  let _ = callfunc "foo" $z in (* 副作用 *)
5:  ()    (* 副作用 = この関数の継続で副作用が起こるため *)
依存解析は、図５(a)のグラフを生成し、 その後支配木は、図５(b)のようである。この処理は、図４のステップ４０２に対応する。
【００４９】
この左のグラフで枝があるところはデータの依存関係を示している、例えば、2 → 4の枝はcallfunc(PHP関数呼び出し)は引数の$zに依存することを表現する。また3 → 4の枝は関数呼び出しの持つ大域的な副作用の間の依存関係として生成される。遅延コードの生成はこのグラフの後支配木を下から再帰的に見ていくことで実現できる。いま、echoやcallfuncは遅延せず、またそれ以外のところは遅延できるかぎり遅延する。例えば、4:let_ = callfunc "foo" $z in[]を処理するときは、その後支配木の親0,2,3について0,2,3の間のデータ依存の関係を保持するようにこの順で上からコード生成をする。また例えば、ノード2を処理するときは、まずその親1についてのコード let $y = 2 in []を作り、続いてlet $y = 2 in let $z = $x + $y in []を作る。最後にこのノード2は副作用がないため、これを遅延したコード2’を生成する。ノード3は副作用があるので遅延しない。ここでの処理は、図４のステップ４０４に関連する。結果のコードは以下のようになる。
0’:  let $x = delay (fun _ -> 
0:               1) in 
2’: let $z = delay ( fun _ -> 
1:       let $y = 2 in   
2:       $x + $y)
3: let _ = echo $x in 
4: let _ = callfunc "foo" $z in 
5: ()
【００５０】
ここで、現実には、遅延可能であるものの全てを遅延する必要はない。何を遅延するかは、コストの判断を行って決める。例えば、echoのように値を即座にしかも必ず使うことがわかっているものについて、その使う値を遅延する価値はないので 0’の遅延は不要である。 もしechoがない場合に0’のような定数を遅延するどうかは判断が分かれる。 もし定数が連想配列やデータ構造であるならば、遅延してもよい。なぜなら、後の部分評価の仕組みを使って定数データへのアクセスのコストを省略することができる可能性が高いからである。あるいは、このような定数は遅延しないとし、定数データ構造からの読み出しとクロージャの強制とは別個にプロファイルしてから、レベル２コンパイラにフィードバックしてもよい。$z = $x + $y の計算などは、PHPではある程度コストが高いので遅延してもよい。もしこれが連想配列の操作などであれば、さらにコストが高いので遅延する。
【００５１】
＜データ構造の更新を遅延する場合の注意点＞
PHPの場合、連想配列のデータ構造は、デフォルトではエイリアスを含まない。すなわち
$x = null;
$x["key"] = "hello";
$y = $x;
$y["key"] = "world";
echo $x["key"]; // hello
【００５２】
この２行目の代入は、Javaのオブジェクトの場合のようなエイリアス生成ではなく値のコピーとなる。本発明のコンパイラでは、PHPは連想配列を不変の値として扱っていると考え、上のプログラムを下のようなヒープへの副作用を考えないプログラムに変換する。
let $x = null in . 
let $x = update "key" "hello" $x in
let $y = $x in 
let $y = update "key" "world" $y in
echo (load "key" $x)
【００５３】
実行時には、このようなupdate操作は、参照カウントや解析を元に効率のよいヒープへの破壊的な操作に戻してもよい。例えば、参照カウントを使う場合には、$y = $xのところで、コンパイル時には意味を持たない実行時の参照カウント加算が入る。 
【００５４】
問題となるのは、変数やデータ構造の中にエイリアスを生成する参照代入(=&)の操作があった場合である。例えば、$y =& $x["abc"] は、そのようなエイリアスを作る。これによって配列が不変値だという先ほどの仮定が崩れる。このようなエイリアスはupdate操作を＜レベル１コンパイラ＞のところで述べた手法で遅延することを考えるときに問題となる。例えば、以下のような関数を考える。
let foo $x $y =
let $x = update "abc" "def" $x in
let _ = echo $y in
bar $x
【００５５】
まずこのPHP関数がソースプログラム中で function foo($x, $y) {…} のように定義されており、PHPの意味論として値の深いコピーを採用するならば、$xと$yの間にエイリアスがあることはありえないので、いつでも以下のような遅延が許される（なお、値の深いコピーについては、Akihiko Tozawa, Michiaki Tatsubori, Tamiya Onodera, and Yasuhiko Minamide, Copy-on-Write in the PHP Language, In Proceedings of the 36th Annual ACM SIGPLAN - SIGACT Symposium on Principles of Programming Languages (POPL 2009), Savannah, Georgia, USA, January 21-23, 2009, pp.200-212, January, 2009. (C) ACM のPHP(d) あるいは PHP(g)を参照されたい）。
let foo $x $y =
let $x = delay (fun _ -> update "abc" "def" $x)
let _ = echo $y in
bar $x
【００５６】
しかし、PHPの意味論として浅いコピーを採用した場合や、PHP関数が参照渡しで function foo(&$x, &$y) {…} のように定義されている場合はこの遅延は危険である可能性がある。配列の更新が実は $y に影響するかもしれないからである。
【００５７】
本実施例では、この問題については以下の実行系レベルの解決策を使う。すなわち、配列$xの中にエイリアスが存在するかどうかをフラグ$x#contains_aliasで持っておき、このフラグを実行時にチェックして遅延が許されるかどうかを判断する。もしチェックに失敗したら、update操作を実際に行うパスを作る。あるいはパスはひとつのままにして、いま作った遅延を強制してしまう処理を加えてもよく、これで遅延が起きなかったのと同等のことになる。すると、コードは以下のようになる。
let foo $x $y =
let delay_ok = not $x#contains_alias in
let $x = delay (fun _ -> update "abc" "def" $x)
let $x = if delay_ok then $x else force $x in
let _ = echo $y in
bar $x
【００５８】
フラグ$x#contains_aliasは、$y =& $x["abc"]のような参照代入の際にセットすることができる。
【００５９】
PHPのオブジェクトについても、同様の検査で遅延をすることができる。注意点として, 以下のようなプログラムに＜レベル１コンパイラ＞のアルゴリズムを適用するときには、$o1と$o2の操作の間に依存がある可能性があるので、これはグラフの枝として追加しておく必要がある。この枝は必ず依存があることを意味するわけではないため、$o1からecho文への依存があるからといって、$o1の遅延をあきらめる必要はない。
function foo($o1, $o2) {
$o1->name = "akihiko";
$o1->address = "yamato";
echo $o2->name;
bar($o1);
}
【００６０】
上記プログラムは、下記のように遅延される。 ただし、本実施例のコンパイラでは、PHPのオブジェクト$o1は、フィールドを表現する連想配列$o1#filedsへのポインタ参照を持つ値として扱っている。操作<-は書き込み可能レコードの更新を表している。
let foo $o1 $o2 = 
let fields = $o1#fields in
let delay_ok = not fields#contains_alias in
let fields = delay (fun _ -> 
let fields = update "name" "akihiko" fields in
update "address" "yamato" fields)
in
let _= $o1#fields <- (if delay_ok then fields else force fields) in
let _ = echo (load $o2#fields "name") in
bar $o1
【００６１】
もし、$o1と$o2のルートがエイリアスしていれば、load処理の際に$o2#fileds配列の更新が強制されるので安全である。オブジェクトの更新を遅延するとまず、配列の場合と同等のコストが省ける。PHPは型無し言語であるため、その処理系ではフィールド操作が連想配列の操作（ハッシュテーブル操作）で表現されているためである。さらに、PHPでは、フィールドのデフォルト値をクラス宣言中に書くことができる。処理系によっては、これらの値はコンパイル時定数の配列に保存し、フィールド読み込みの際にその定数配列をそのまま使う。この場合、オブジェクトに書き込みが行われる時点ではじめてこの配列の各オブジェクト用の複製が作られるが、この複製のコストはかなり大きくなる。もし次に述べる部分評価のテクニックを使いオブジェクトへの書き込みがプログラム終了まで遅延できればこのコストを除去することができる。
【００６２】
＜副作用の種別を考えたより精緻な遅延の生成＞
＜レベル１コンパイラ＞の節で説明した手法でのデータ依存グラフ生成の際に, 副作用間の詳細なデータ依存関係を明らかにするようにするとより精緻な遅延が可能になる。これにより、例えば前述のように、グローバルテーブル操作が副作用で表現されている場合でも、操作を遅延することができるようになる。
【００６３】
副作用の種別を以下のように考える。
- GW: グローバル変数への書き込み
- GR: グローバル変数の読み込み
- IO: IO 処理
- T: 最大の副作用
【００６４】
例えば、前述の例のコードには次のように各let文をラベルづけして副作用を指定できる。
0: letIO _0 = date(DATE_RFC822) in
1: letGW _ = upd_global "user" "akihiko" in
2: letGW _ =upd_global "date" _0 in 
3: letT _ = start () in
4: ()
【００６５】
いま、IOとGWの間には干渉がないので、枝は作らない。GW どうし、および、Tから任意の直近の副作用への依存関係を追加して図６(a)に示すグラフと、図６(b)に示す後支配木が得られる。このためには例えば、上からプログラムを見ていき、今までみた副作用の履歴から依存するノードを判断して依存関係を追加すればよい。
【００６６】
GR, GW: 直近のTあるいはGWのうち近い方への枝を追加する。
IO: 直近のTあるいはIOのうち近い方への枝を追加する。
T: 直近がTならば枝を追加する。Tより近いGWおよびIOについては双方への依存を追加する。
【００６７】
あとは＜レベル１コンパイラ＞の節で示したのと同様に遅延生成を追加することができる。
let _0 = date(DATE_RFC822) in
let _ = delay_global (fun _ -> 
let _ = upd_global "user" "akihiko" in
upd_global "date"_0)
in 
start () 
【００６８】
ただし、この場合の注意点として、グローバル変数テーブルの遅延クロージャを登録するためにはdelay_globalという特殊な操作を使う必要がある。実行系は、このような副作用を含む遅延クロージャについては各副作用について常にもっとも最後に登録されたクロージャを覚えておき、さらに、delay_globalが新しいクロージャを登録する際にはこのクロージャからもっとも最後に登録されたクロージャへのリンクを覚えておく必要がある。クロージャが強制されるときには、このリンクを辿って、過去にさかのぼってリンク上のすべてのクロージャを強制する。
【００６９】
グローバル変数については、各変数の更新について別々に遅延を考えることもできる。この場合にはGW/GRのアノテーションを各変数に関するより詳細なものにする必要がある。 
【００７０】
＜プロファイラ＞
プロファイラは、遅延がどこで強制されたかをプロファイルする。以下で述べる処理は、図４のステップ４０６に対応する。いま、データの遅延の強制は通常データの読みこみ (例えばecho文)や、データへの書き込み (連想配列のupdate操作) などのためにライブラリ内で起こるが、実際のプロファイルはそれよりも外側のユーザレベルコードの地点で行いたい。理由は：
- PHPでは連想配列などのデータ構造の操作などは、Ｃで書かれたネイティブ・ライブラリ内で起こるため、そのライブラリ内部のレベルでのプロファイルはしにくい。
- またライブラリは多くの箇所から呼ばれるため、ライブラリ内 (例えば、echoの実装の中で) で遅延強制のプロファイルを行った場合、よくヒットするようなガードは生成できない可能性が高い。
【００７１】
この問題を解決するために、プロファイラは、ユーザ・レベル・コードの地点で実際のデータの遅延の強制よりも早いタイミングで値のプロファイリングを行う。具体的には、レベル１コンパイラが適切なprofileコードを生成コード中に入れるなどの手法が考えられる。いまPHPに関して引数の値を即座に使うことがわかっているライブラリ操作とその引数、例えば：
- echo xの引数x
- update k v xのキーkおよび配列x (配列に入る値vは即座には使わないことに注意)
- ・・・
を列挙することができる。このような操作と引数の組に対して、レベル１コンパイラが、ユーザレベルコード中に 
let x = profile x 0 in echo x
let k = profile k 1 in let x = profile x 2 in update k v x
のような形でprofile操作を入れる。このprofiｌe操作の第１引数は遅延であるかもしれない値であり、第２引数はすべてのprofile操作に対してユニークなコールサイトの識別子である。コードの実行時にはprofile x id 操作はプロファイルする値xとコールサイトの識別子idを引数として取り、もしxが遅延クロージャ(fn, record)であれば、このfnとコールサイト識別子idとの組を大域的な場所に記憶しておく。
【００７２】
＜レベル２コンパイラ＞
レベル２コンパイラは２つの処理を行う。 
1) 各コールサイト識別子idについて対応するprofile操作で高頻度で強制されたクロージャのコードfnがあると判定された場合、このコードをガード付でコールサイトにインラインする。これは、図４のステップ４０８に対応する。
2) 結果として得られたコードを部分評価によって、最適化する。
【００７３】
1) に関しては、具体的にはレベル１コンパイラが出力したprofile操作を、ガードつきでfnの中間コードに置き換えてしまえばよい。このとき、後続のコードのバージョニング(versioning)も行わないと後の部分評価で効率のよいコードが得られない可能性があるが、この方法は既知なので、詳しくは述べない。
【００７４】
2) に関しては部分評価器の実現の手法の一例を示す。
LetGW _ = upd_global "user" "akihiko" in 
LetGW _ = upd_global "date" record#_0 in
LetGR _0 = load_global "user" in 
letGR _1 = load_global "date" in    
echo ("user " . _0 . "logined at " . _1) 
【００７５】
上記のクロージャのインライン後のコード・フラグメントを考える。まず部分評価器は通常、環境などの副作用のパラメタを明示的にしたほうがやりやすいので、これを明示的にする。
fun global->
let global = upd_global "user" "akihiko" global iｎ
let global = upd_global "date" record#_0 global in
let _0 = load_global "user" global in
let _1 = load_global "date" global in
echo ("user " . _0 . "logined at " . _1) 
【００７６】
この変換は、副作用のアノテーションに基づいて
letGW _ = e1 in e2 -> fun global -> let global = e1 global in e2 global 
letGR x = e1 in e2 -> fun global -> let x = e1 global in e2 global    
と書き換えたあとで、これをベータ簡約で簡単化すれば実現できる。
【００７７】
さて、いまひとつの問題として、普通の定数畳み込みでは、record#_0のような実行時の値を連想配列上に計算することができない。そのため、実行時ではなくコンパイル時のupd_global, load_globalを以下のように定義する。
upd_global key val arry = fun cons nil -> cons key val (arry cons nil)
load_global key arry = arry (fun key’ val a -> if key = key’ then val else a) error
【００７８】
この手法は、実行時配列の代わりに、チャーチ符号化(Church encoding)された key valのリストをコンパイル時配列の表現とすることであり、上の定義はこのような配列を処理する関数である。このようなチャーチ符号化でなくても、通常のリストを使ったコードを生成しても同様のことは可能である。ポイントは、データ構造の中身が中間コード中で明示的にあらわれるような (要は関数型言語の) 中間言語を使うことで、実行時の値を含むデータ構造が表現できることである。
【００７９】
上の定義を上のプログラムにインラインした後、プログラムの網羅的なベータ簡約および定数畳み込みを行うと、以下のようなコードを得ることができる。
fun global ->  echo ("user akhiko logined at " . record#_0) 
【００８０】
あとは、一旦明示的にした副作用のパラメタを、再び隠蔽すれば、欲しい形のコードを得ることができる。
echo ("user akhiko logined at " . record#_0)
【００８１】
注意点として、うまく部分評価ができなかった場合には、コンパイル時配列の操作 (リストの操作なので遅い) ではなく、実行時配列の操作 (ハッシュテーブル操作の呼び出し) を残余コードに残さないと効率が落ちるという点がある。このための工夫は、例えば住井の部分評価器 (コンパイル時の値と残余コードの組をかえすようなプログラムの評価器( Eijiro Sumii and Naoki Kobayashi. A Hybrid Approach to Online and Offline Partial Evaluation. Higher-Order and Symbolic Computation, Kluwer Academic Publishers, the Netherlands, vol. 14, no. 2/3, pp. 101-142, September 2001. (.dvi, .ps.gz, .pdf, abstract)参照) に、コンパイル時配列の操作は、コンパイル時の値のみとして、結果コードには見せないような改変を加えることで実現できた。
【００８２】
＜遅延の除去＞
以下の処理は、図４のステップ４１０に対応する。遅延処理にはコストがかかるので、もし遅延のコストを上回るような利得がレベル２コンパイルによって得られない場合は、 遅延処理をキャンセルするような再コンパイルを行ってもよい。このようなケースには次の二通りがあると考えられる。
【００８３】
1. 遅延を生成したが、それが拡張ライブラリの中など、遅延をプロファイルによって捕獲できない場所で高頻度で強制されることが判明した場合、または遅延がユーザーコード中で使用されるが、その頻度があまり高くないため、レベル２コンパイルで使用サイトをこの遅延コードのために最適化するとの判断がされなかった場合。
2. 遅延を生成し、それがユーザーレベルコード中で高頻度で強制されるが、レベル２コンパイルを試行してみたところ、あまり性能があがるようなコスト削減が期待できなかった場合。
【００８４】
ここで、１のケースは、プロファイラが判別することができる。２のケースの判断手法は、ヒューリスティクスに頼るしかないが、例えば、部分評価後のコードが部分評価前と較べ、どの程度のコスト削減を実現しているかを推測するようなヒューリスティクスが考えられる。いま、連想配列を扱っている場合ならば、単純に結果コードの中に出現するload/update操作の回数が、部分評価前と比べてどの程度削減しているかを調べれば、ある程度の推測が可能だと考えられる。もし、１回の遅延評価のインラインあたり削減された処理の回数が設定された閾値よりも小さければ、生成された遅延を取り消すとの判断をする。 
【００８５】
一旦生成された遅延が取り消されるべきであると判断された場合には、該当する遅延生成を含むコードについてレベル１コンパイルを再実行し、該当部分について遅延を行わないようなコードを生成して元のコードを置き換えればよい。
【００８６】
なお、上記実施例は、プログラミング言語としてPHPを例にとって説明したが、本発明はこれには限定されず、Java(R)など、遅延評価が行われる任意の言語に適用可能である。
【００８７】
また、示されている例は、スタンドアロンであるが、通常PHPが使用される、サーバ上でのコンパイル環境を想定してもよい。
【符号の説明】
【００８８】
１０２  システム・バス
１０４  ＣＰＵ
１１０  キーボード
１１２  マウス
１１４  ディスプレイ
１０６  主記憶
１０８  ハードディスク・ドライブ
２０２  ソース・プログラム
２０４  変換モジュール
２０６  コンパイラ
２０８  実行系
２１０  プロファイル情報
【図１】
2011065220.tif 000003
【図２】
2011065220.tif 000004
【図３】
2011065220.tif 000005
【図４】
2011065220.tif 000006
【図５】
2011065220.tif 000007
【図６】
2011065220.tif 000008