アスペクト指向プログラミングの 動的プログラムスライスへの応用 石尾 隆, 楠本 真二, 井上 克郎 大阪大学大学院基礎工学研究科
研究の背景 ソフトウェアの大規模化 フォールト特定、修正のコスト増大 実際に動くコードは、全体のごく一部 テストケースからのフォールト特定 扱うコード量が多いと効率が悪くなる 実際に動くコードは、全体のごく一部 まず研究の背景について説明します。近年、ソフトウェアの大規模化に伴い、コード中からフォールトを特定し、修正するという作業のコストが大きくなってきました。基本的にはテストケースから、そのフォールトを特定するという作業が行われていますが、複雑な挙動をするコードも多いため、フォールトを特定することが難しくなってきています。 しかし、あるテストケースに対して実際に動いたコードというのは、全体のうち、ごく一部に限られていることから、無関係なコードを自動的に除外して、フォールト位置の特定を迅速に行うことが求められるようになってきました。 関係のないコードを自動的に除外して 迅速なフォールト特定を行いたい
プログラムスライス プログラム中のある文sのある変数v (スライス基点)の値に影響を与えうる文の集合 デバッグ対象が限定され、フォールト位置特定を効率よく行うことができる 1: a = 5; 2: b = a + a; 3: if (b > 0) { 4: c = a; 5: } 6: d = b; 1: a = 5; 2: b = a + a; 3: if (b > 0) { 4: c = a; 5: } 6: d = b; スライス基点(6, b)に対するスライス そのための技術のひとつとして、プログラムスライスが提案されています。これは、プログラム中のある文 sのある変数 v 、これをスライス基点と呼びますが、これに影響を与える文の集合を抽出するというものです。例で示すと、このようなプログラムに対して、スライス基点として 6行目の変数 b を選ぶと、このようなコードを得ることができます。 このようにして扱うコード量を減らすことで、フォールト位置特定を効率よく行うことができます。 以後、プログラムスライスを単にスライスと呼ぶことにします。
DC(Dependence Cache)スライス 12: b = a; a データ依存関係 1.依存関係解析 動的データ依存関係 静的制御依存関係 2.プログラム依存グラフ(PDG)構築 節点:各プログラム文 有向辺:文間の依存関係 3.グラフ探索 スライス基点に対応する節点から 有向辺を逆向きに探索 20: if (a < 1) { 21: b = a; 22: } 制御依存関係 プログラム依存グラフ (PDG) スライス計算には方法がいくつかありますが、その中で、コストと得られるスライスのバランスが取れているDC(Dependence Cache)スライスについて説明します。 スライス計算は、プログラム中の依存関係を解析し、プログラム文を節点、依存関係を辺とするプログラム依存グラフを作成することで、グラフ探索問題に置き換えられます。 依存関係の解析ですが、DCスライスではデータ依存関係を動的に、制御依存関係を静的に行います。データ依存関係というのは変数の値の定義と参照に対応する関係のことです。例で示すとこのように、10行目で定義した変数から12行目での参照へ、依存関係があるということになります。プログラムを実行して、このデータ依存関係を収集していきます。制御依存関係は、いわゆる条件文などの実行制御文において、条件節がそのブロックの実行を制御することから、この例のように条件節から実行文へ、依存関係が生じるものとします。こちらは、ソースコードを静的に解析することによって得ることができます。 このような解析結果からプログラム依存グラフを構築し、スライス基点に対応する頂点を選び、そこからグラフを逆向きに探索することで、対応するスライスを得ることができます。 † Y.Ashida, F.Ohata and K.Inoue: “Slicing Methods Using Static and Dynamic Information”, Proceedings of the 6th Asia Pacific Software Engineering Conference , pp.344-350, Takamatsu, Japan, December (1999).
既存の手法 Java Virtual Machine (JVM)の改造† → ソースコード側に依存関係解析処理を追加する 細粒度でのスライスが可能 特定のJVMの実装、バージョンに依存する プログラムがそのバージョンのJVMで動くかどうかの検証は困難 コンパイラによる最適化を抑止する必要あり → ソースコード側に依存関係解析処理を追加する DCスライスの計算では動的なデータ依存関係の解析が重要となります。 現在、オブジェクト指向言語として一般に用いられている Java を対象とした場合、実行環境である Java Virtual Machine, JVM にその機能を付加する方法で実現されています。 JVM の改造という方法では、バイトコードレベルでのデータ依存関係を取得することができるため、細粒度でのスライスが可能になり、スレッド管理をはじめとする多くの実行環境情報を活用することができます。そのかわり、元になった JVM の実装に依存してしまうため、本体のバージョンアップへの追随などの作業が必要になります。また、プログラムがそのバージョンの JVM で動作するかどうかを確認するのは一般に困難ですし、加えて、コンパイラによるバイトコード最適化によって正しい結果が得られなくなることがあるため、最適化を抑制する必要があり、その結果実行時にパフォーマンス上のオーバーヘッドを生じることになります。 JVMのバージョン依存性を受けないためには、JVMの改造ではなくソースコード側にデータ依存関係解析処理を追加したいということになりますが、この場合のようなシステムのクラス群全体に埋め込むようなコードの扱いを容易にするためのパラダイムとして、アスペクト指向プログラミングというものを紹介します。 † 誉田謙二, 大畑文明, 井上克郎: Javaバーチャルマシンを利用した動的依存関係解析手法の提案, SIGSS-2002 Jan
アスペクト指向プログラミング 複数のクラスを横断する要素をモジュール「アスペクト」として記述するプログラミング手法 ある特定の目的に関する処理が、複数のクラスに分散することを避ける ソースコード上では、ひとつの単位にまとめて記述 コンパイル時、実行時にクラスに結合する AspectJ: Java 用アスペクト記述・処理系 http://aspectj.org/ アスペクト指向プログラミングとは何か、ということについて簡単に説明します。オブジェクト指向プログラミングではオブジェクトというカプセル化単位を用いていましたが、複数のクラスを横断するような、カプセル化できない要素を新しいモジュール単位、アスペクトとして記述しようというものです。これは、ある特定の処理に関する記述が複数のクラスに分散することを避けることを目的としています。ソースコード上ではまとまった単位として記述しておき、処理系にもよりますがコンパイル時あるいは実行時に、通常のプログラムに変換してやろうというものです。Java 用の処理系としては、AspectJ などがあります。
Session Managing Aspect アスペクトの例 HTTP Request HTTP Session HTTP Response クラスによる実現 Session Managing Code Session Managing Code Session Managing Code アスペクトをクラスに結合する (Aspect Weaving) AspectJ HTTP Request HTTP Session HTTP Response クラスによる実現と、アスペクトによる実現との比較を、図で説明します。これは HTTP でのセッション管理の例で、上側がクラスのみによる実現、下側がアスペクトを利用した実現です。 ここではセッション管理のコードが Session, Request, Response の三つのクラスに分散しており、相互に依存した構造になっています。これらのクラスに対するテスト、変更、再利用は、このクラス群をまとめて扱わなければなりません。 その分散するはずのコードをアスペクトとして、クラス本体から切り離し、クラス間の依存性を簡略化することができ、また1モジュール内に簡潔に処理を記述できるようになります。実際にコードを実行するときは、これをAspectJのような処理系でクラスだけの実現に変換してやることで、標準の JVM でプログラムを実行することができます。 データ依存関係解析の処理も、このようなコードと同様に各クラスに分散する処理ですので、アスペクトを用いることで簡潔なモジュールとして記述できる、と考えられます。 アスペクトによる実現 Session Managing Aspect
アスペクトの記述 アスペクト = 結合の基準 + 結合するコード 動的データ依存関係解析アスペクト 主要な結合の基準 結合するコードの特徴 メソッド呼び出し、メソッド実行 フィールド代入、参照 メソッド、フィールドの属するクラスに関する条件 結合するコードの特徴 通常の Java で記述される 実際の結合位置に関する情報へアクセスできる 動的データ依存関係解析アスペクト フィールドへの代入: 代入のソースコード内での位置を記録 フィールドの参照: そのフィールドへの最後の代入位置を取得し、動的依存関係を記録 アスペクトは、基本的には処理の中心となるプログラムコードと、それをクラスのどこに結合するかという結合の基準のセットによって記述されます。AspectJ では、結合の基準として、メソッド呼び出しの直前、直後、メソッド本体の先頭と最後、フィールドの代入、参照、そしてメソッドやフィールドの属するクラスに関する条件などが利用できます。 実際にそこに結合されるコードは、通常のJavaで記述されますが、AspectJ では、プログラム内からこの結合位置に関する情報へアクセスできます。 そこで、動的データ依存関係解析のアスペクトは、その要点だけ説明すると、フィールドへの代入というところに、そのソースコード位置を記録する処理を結合し、フィールドの参照というところに、そのフィールドへの最後の代入位置を取得し、動的依存関係を記録するという処理を結合する、ということで実現できます。
アスペクトによる実現の特徴 利点 AspectJ における制限 高い抽象度で、結合ルールを明快に記述できる データ依存関係解析処理をカプセル化できる 特定のJavaのバージョンに依存しない コンパイラによる最適化の影響を受けない AspectJ における制限 ローカル変数に対してはアスペクトを結合できない →静的解析で補う必要がある アスペクトによる実現の利点をまとめます。 アスペクトでは高い抽象度でコードの結合ルールを記述でき、また、解析処理をカプセル化できるためコードの保守が容易になります。また、 JVMの特定のバージョンに依存せず、コンパイラによる最適化の影響を受けない、ということにもなります。 今回はツールとして AspectJ を用いましたが、その実装上の制限として、ローカル変数にはアスペクトを結合することができない、というものがありましたので、これは静的な解析で補うということにしました。
アスペクトによる依存関係解析 スライスツールの実装(Java) AspectJ によるデータ依存関係解析 約 16,000 行(コメント含む) AspectJ によるデータ依存関係解析 依存関係解析アスペクト+クラス: 300行 JVM改造アプローチでは、16,000 行程度の追加コードが必要(全体で約 580,000 行) 実際に、その差がどのようなものとなるのかを確認するために、スライスツールを Java で実装しました。コードサイズはコメントを含めて約16000行、先ほど述べたようなデータ依存関係解析モジュールは、約 300 行となりました。JVM改造のアプローチではJavaのコンパイラとVMで50万行をこえるコードに16000行ほどのコードを追加するのに比べて、はるかに小さなコード量で済ませることができました。
ツールの動作 スライス 計算 スライス結果 依存関係解析 アスペクト スライス対象 ソースコード AspectJ アスペクト結合済み クラスファイル 通常の JVM 依存関係情報 通常の実行結果 ツールの動作について説明します。 最初にスライスをとりたいプログラムのソースコードと、依存関係解析のアスペクトを一緒に AspectJ でコンパイルし、アスペクトを結合したクラスファイルを生成します。この結合結果は通常の Java のプログラムとなっているので、普通の JVM 上で実行することができ、それによって通常の実行結果とあわせて、依存関係情報をファイルに出力します。この情報から得られた動的依存関係と、ソースコードを解析して得られた静的依存関係から、最終的にスライスを計算・出力します。
評価: スライスのサイズ JVM 改造による実現との比較 適用対象 P1: 簡易データベースプログラム (4クラス、262行) ローカル変数の扱いの差 による影響はほとんど見られない 短いメソッドが多いため 単位: 行 このツールを用いて、JVM改造によるアプローチとの比較実験を行いました。 テストケースとして、簡易データベースプログラム、ソーティングプログラム、スライス計算プログラムを与え、スライスを計算してみました。得られた結果を右下の表に示してあります。これは基本的に行数による比較を行っていますが、アスペクトと JVM ではほとんど差はありませんでした。アスペクト側のほうが、ローカル変数に関して静的な解析で補うという都合上、スライスに余計な文も加わる、つまりスライスサイズが大きくなる、という予想がありましたが、実際にはアスペクトのほうが小さくなる、という結果になりました。実際に得られたスライスを確認したところ、ここで生まれた差の原因ですが、行の数え方、たとえば2行に分かれた式をアスペクトは1行として数えており、JVMでは2行として数える、といった差が、ローカル変数の扱いの差よりも大きく効いてきたことにありました。プログラムには短いメソッドが多く、ほとんどローカル変数が使われていなかったこともその原因と考えられます。 アスペクト JVM P1 33 30 P2 25 28 P3 226 240
評価: 実行コスト 同じ入力を与えて実行時間を比較 適用対象はスライスサイズの場合と同様 コンパイラの最適化による影響 JVMは、すべてのクラスに対し、解析コストを支払う ライブラリが多いプログラムほど、JVM のアプローチは不利 通常実行 アスペクト JVM P1 240 ms 343ms(x1.4) 1829 ms(x7.6) P2 242 ms 416ms(x1.7) 2846 ms(x11.8) P3 0.7 sec 9.0 sec(x12.9) 79.3 sec(x113.3) 次に、実行コストに関して比較を行いました。 先ほどと同様、三つのプログラムに対して、アスペクトとJVMで同じ入力を与えて実行し、その実行時間を計測しました。結果は表の通りです。 表はプログラムを通常実行した場合と、アスペクトを結合して実行した場合、改造JVMで実行した場合の3通りでの実行時間です。括弧内の値は、それが通常実行に対して何倍になるか、という値です。 一般に、アスペクトのほうが JVM 側に比べて高いパフォーマンスを発揮しました。この原因としては、まずコンパイラの最適化の影響が考えられます。また、JVMのアプローチが、ライブラリなどを含むすべての実行に対して、データ依存関係の解決のオーバーヘッドを支払っていることから、規模が大きくなり、ライブラリを多く使うプログラムほど、JVM側のアプローチは実行コスト上不利になったと考えられます。
まとめと今後の課題 アスペクトを用いた動的データ依存解析 課題 アスペクト指向プログラミングによって、動的情報取得モジュールの取り扱いが容易になる JVM 改造アプローチに比べ、わずかなスライスサイズの変化で、高速化できる 課題 大規模プログラムへの適用 手法のスケーラビリティ 最後にまとめと今後の課題について述べます。 アスペクトを用いた動的データ依存関係解析では、アスペクト指向プログラミングによってデータ依存解析モジュールの取り扱いが容易になり、また、JVMアプローチでは犠牲になったバージョン独立性や移植性の確保を行うことができました。 また、JVM改造アプローチに比べ、わずかなスライスサイズの変化で、大幅なパフォーマンス改善を実現することができました。 今後の課題ですが、今回の実験では、まだ小規模から中規模程度のプログラムしか対象としていないため、大規模プログラムに対してこれらの手法を適用し、スケーラビリティについて確認する、ということが挙げられます。 以上で、発表を終わります。