第 3 章 動的ロード の実現
3.2 JeanPaul における動的ロード の実現
した静的コンパイル済みコード はリンクされず,無効になる.
(b)一致した場合,クラスCについて,クラスファイルの同一性が確認できた結果とし て利用可能になった静的コンパイル済みコードがあるかど うか調べ,あったらJava VMにリンクする.
静的コンパイル済みコード のリンクは静的コンパイル済みコード のアドレスを図
3.1のリンクテーブルに登録すること実現できる.リンクテーブル上のメソッド を 表すエントリには,初期状態ではインタプリタを呼び出すスタブ 関数のアドレスが 登録してある.これは静的コンパイル済みコード をリンクするまでの間,プログラ ムの実行をインタプ リタにおこなわせるためである.
リンクはメソッド 単位でおこなう.メソッド の静的コンパイル済みコードが実行 時に利用可能になる条件は,メソッド の全仮定クラスについて,静的コンパイル時 に参照したクラスファイルと動的ロードしたものが同一であるとの確認が済むこと である.
個々のメソッド の全仮定クラスを収める集合は仮定クラス情報に記録してある.
静的コンパイル済みコード リンク機能は,利用可能な静的コンパイル済みコードが あるかど うか調べるために,仮定クラス情報とクラスファイルの同一性確認が済ん だクラスの集合を,照しあわせる.
本論文ではプログラムの実行を開始してから静的コンパイル済みコード のリンクが完了 するまでの期間をリンクの遅延と表現する.リンクの遅延は短い方がプログラムの実行を 高速化できる.なぜならリンクの遅延を短くすると,実行オーバヘッドが大きいインタプ リタを使ってプログラムを実行する期間が短くなるからである.
静的コンパイル済みコード を利用するJava VMでは,このタイミングが実行速度に影響 する.なぜなら,このタイミングを早くすると,リンクの遅延が短くなるからである.こ こではまず,このタイミングを操作してリンクの遅延を最小限にする方法を紹介し ,次に
JeanPaulにおいてクラスを動的ロード するタイミングを示す.最後に,JeanPaulではク
ラスを動的ロード するタイミングがリンクの遅延に影響しないことと,その理由を示す.
リンクの遅延を最小限にする方法
リンクの遅延を最小限にするには,静的コンパイル時に参照した全クラスをJava VM
の起動時に動的ロード すればよい.そうすれば 静的コンパイル済みコード をリンクする作
業をJava VMの起動時に一括して実施できる.
JeanPaul
における動的ロード のタイミングJeanPaulのJava VMでは,静的コンパイル時に参照したクラスを特別扱いせず,他の
クラスと同様にクラスを初期化する時点で動的ロード する.JeanPaulでリンクの遅延を
最小限にする方法を採用しなかった理由は,リンクの遅延を最小限にする方法に次に示す
2つの問題があったからである.
1.アプ リケーションの起動時にクラスファイルを参照するためのファイルアクセスが集 中し ,起動に時間がかかる.
2.実行するJavaアプ リケーションによっては挙動が不自然になる.挙動が不自然にな るのは,実行時に生成したクラスファイルを動的ロード するアプリケーションである.
このようなアプ リケーションの作者は実行時に生成したクラスファイルを動的ロード すると考えるのが一般的だが,アプ リケーションの起動時にあらかじめ読み込んでお く方法では,前にアプリケーションを実行したとき生成した古いクラスファイルを読 み込んでしまう.
なお,JeanPaulではクラスを初期化する時点で動的ロード するが ,これより早いタイ
ミングで動的ロード をおこなったとしても,JeanPaulのJava VMが静的コンパイル済み
コード をリンクするタイミングは変らない.なぜなら3.1節で述べたように,JeanPaulの
Java VMがクラスファ イルの同一性を確認して静的コンパイル済みコード をリンクする
のは,クラスを動的ロードした直後ではなく,クラスの初期化完了後だからである.
クラス初期化検査の除去
JeanPaulが静的コンパイル済みコード をリンクする作業をクラスの初期化完了後にお
こなう目的は,Java2Cトランスレータにおいてクラス初期化検査を除去するためである.
たとえば 図2.1(a)に示すJavaのソースコード 上におけるクラスフィールド 参照にクラ
ス初期化検査の除去を適用し,C言語のソースコード に変換した結果を,図2.1(c)に示す.
図2.1(c)のコード はクラス初期化検査をもたない.したがって実行時にクラスフィールド
を定義するクラスCを初期化するより前に図2.1(c)のコード を実行すると,クラスCを
初期化するタイミングがおかし くなり,プログラムを正常に実行できなくなる.JeanPaul
ではこの問題を次の手順で解決する.
1.実行時にJava VMは,3.1節で述べたように,クラスの初期化完了後にクラスファイ
ルの同一性を確認する.したが って,メソッド n()の仮定クラスの集合をSnとする
とき,メソッド n()の静的コンパイル済みコード をJava VMにリンクするタイミン
グは,Snに属する全クラスについて初期化とクラスファ イルの同一性確認が済んだ 後になる.
2. Java2Cトランスレータは メソッド n()の中にあるクラスCの初期化検査を除去した
ら,集合SnにCを追加する.これにより,メソッドn()の静的コンパイル済みコード をリンクするタイミングが実行時にクラスCを初期化した後になることを保証する.
ここで提案したクラス初期化検査除去の実現は,2.3節で示した過去の技法とは異なり,
コード を上書きしないためJava2Cトランスレータで利用できる.ただし ,ここで提案し た技法を使うとリンクの遅延が長くなる.つまり,実行時に検査対象のクラスの初期化が 済むまで静的コンパイル済みコード をリンクできなくなり,その間の実行をインタプ リタ でおこなうことから,実行速度が低下し うる.ここで提案したクラス初期化検査除去の実 現と,その副作用であるリンクの遅延の延長が実行速度に与える影響は,4章で評価する.
3.2.2
仮定クラスの集合を計算する手順3.1節で述べたように,JeanPaulのJava2Cトランスレータは動的ロード を実現するた めに,トランスレート対象の個々のメソッド について,メソッド の全仮定クラスを収める 集合を計算する.計算の手順を次に示す.
1. Java2Cトランスレータは,トランスレート作業の開始にあたり,トランスレート対
象の個々のメソッドn()に対して集合Snと配列Nnを用意する.
集合Snは メソッド n()の全仮定クラスを収める.集合Snの初期値は,メソッ
ドn()を定義するクラスがCであるとき,Cだけを要素にもつ集合になる.こ れは メソッド n()のトランスレート開始時点では,メソッド n()の定義を含む
クラスCだけを参照することを意味する.
配列Nnには,メソッド n() の静的コンパイル済みコード から直接呼出し する メソッド を格納する.
配列Nnが集合でなく配列である理由は,インライン展開の実現を簡単にするため
である.インライン展開を適用すると直接呼出しが消失するので,その結果をNnに
反映する必要がある.反映する処理を,Nnから直接呼出し 先のメソッド を1つ除去
するだけの簡単な処理で実現するには,Nnを配列にして,n()の中にある直接呼出
し箇所と同じ 数だけ,直接呼出し先のメソッド を記録しておく必要がある.
2.全てのメソッド をC 言語のソースコード に変換する.個々のメソッドn()を最適化
する過程で別のクラスを参照した場合には集合Snに要素を追加し ,直接呼出しを生 成した場合には配列Nn に要素を追加する.Java2Cトランスレータが 実施する最適 化一覧を表3.1に示す.表3.1に示した最適化のうち,※印をつけたものが集合Snや
配列Nn に要素を追加する.表3.1に示した最適化の詳細と,最適化が集合Snや配
列Nnに与える影響については3.3節で述べる.ただし ,クラス初期化検査の除去に ついては3.2節で述べた.
表3.1では最適化を,クラス間最適化とクラス内最適化に分類している.クラス間 最適化は原則とし て集合Sn や配列Nnに要素を追加するので※印がついている.中 には※印がついていないものもある.それは先行するクラス間最適化で参照したクラ スのみを参照するクラス最適化である.
3.全てのメソッドをC言語のソースコード に変換しおわったら,個々のメソッドn()に
ついて,メソッドn()から直接呼出し経由で到達し うるメソッド の集合Mnを計算す
る.計算の手順を次に示す.
(a)集合Mnの初期値を空集合とする.
(b)集合Mnに配列Nnが含む全メソッド を追加する.
(c)集合Mnが含む個々のメソッドm()について,配列Nmが含む全メソッド を集合
Mnに追加する.
メソッド の追加にあたり,追加するメソッド l()を集合Mnが含んでいなかっ
たら,メソッドl()を集合Mnに追加してから,配列Nlが含む全メソッド も
集合Mnに追加する.
集合Mnが計算できたら,集合Mnが含む個々のメソッド m()について,集合Sm
が含む全クラスを集合Snに追加する.これで メソッドn()の全仮定クラスから成る 集合Sn の計算は終了である.
直接呼出し 経由で到達し うるメソッド m()の仮定クラスを収める集合Smが含む全ク
ラスをSnに追加する目的は,まだ使えない静的コンパイル済みコード を直接呼出し 経由 で呼び 出すことがないようにするためである.直接呼出し 先m()の静的コンパイル済み
コードが,クラスファイルの同一性確認が完了しておらず,まだ使えない状況にあるにも かかわらず,直接呼出元n()の静的コンパイル済みコード をリンクしてし まうと,直接呼 出元n()の静的コンパイル済みコード 経由でまだ使ってはいけない直接呼出し 先m()の
静的コンパイル済みコード を呼び 出してし まい,プログラムが正常に実行できなくなる.