プログラムの領域を ポイントカットする ことが可能な

プログラムの領域を
ポイントカットすることが可能な
アスペクト指向言語
赤井駿平 千葉滋
東京工業大学
背景:同期処理の粒度

並列処理は同期処理の粒度により実行速度が
変化する
–
粒度が細かい方が並列に実行されやすい

–
通常はこちらが速い
スレッドの数が多い場合


同期処理自体やコンテキストスイッチのオーバーヘッドが増
加
粒度が粗い方が速くなる場合がある
–
状況に応じて最適な粒度が異なる
Javassistでの同期処理の例

Javassistのバグレポート
[JASSIST-28 :
http://jira.jboss.org/jira/browse/JASSIST-28]
–

粗い粒度を選択
同期処理をアスペクトと
して分離して記述できる
とよい
–
状況に応じて最適な粒度
が選択可能
public class ProxyFactory {
public Class createClass() {
粗い粒度
if (thisClass == null) {
ClassLoader cl = getClassLoader();
synchronized (proxyCache) {
if (useCache){createClass2(cl);}
else {createClass3(cl);}
}}
return thisClass;}
細かい粒度
private void createClass2(ClassLoader cl) {
CacheKey key = new CacheKey(…);
synchronized (proxyCache) {
HashMap cacheForTheLoader =…;
if (cacheForTheLoader == null) {
cacheForTheLoader = new HashMap();
proxyCache.put(cl, cacheForTheLoader);
cacheForTheLoader.put(key, key);
}else {...}}
synchronized (key) {
Class c = isValidEntry(key);
if (c == null) {
createClass3(cl);
key.proxyClass = new WeakReference(…);
}else{thisClass = c;}
}}}
既存のアスペクト指向言語の問題点

同期処理を分離しづらい
–
メソッド内の一部の領域をポイ
ントカットすることは不可

–
メソッド呼び出し,フィールドアク
セス等の実行点またはメソッド全
体のみ
before,afterアドバイスと
mutexの組み合わせ

同期を解放できない場合が存在
before(): …{
mutex.lock();
}
public void foo() {
…
obj.criticalSectionBegin();//ここから同期
throw new RuntimeException();
obj.criticalSectionEnd();//ここまで
….
}
after(): …{
mutex.unlock();
//実行されない
}
提案:領域のポイントカット


AspectJの言語仕様を拡張
メソッド内の領域をポイントカットできる
–
–
選択した領域にbefore,after,aroundアドバイスを適
用可能
制御構造と矛盾を起こさないように領域を選択
ポイントカットしたい領域の指定方法

4つのポイントカット指定子
–
begin

–
end

–
領域の末尾のjoin pointを指定
include

–
ポイントカットしたい領域の先頭のjoin pointを指定
領域の中に含まれなければいけないjoin pointを指定
exclude

領域の中に含まれてはいけないjoin pointを指定
begin/endポイントカット

beginとendにマッチする
join pointとその間をポイン
トカット
–
ただし最短マッチを行う
public void foo() {
…
obj.criticalSectionBegin();//ここから
…
obj.criticalSectionEnd();//ここまで
…
obj.criticalSectionEnd();
…
}
void around():
begin(call(* *. criticalSectionBegin())) &&
end(call(* *. criticalSectionEnd()))
{
synchronized(someObject){
proceed();
}
}
include/excludeポイントカット
begin(call(* *. criticalSectionStart())) &&
end(call(* *. criticalSectionEnd())) &&
include(call(* *.a()))

begin,endが同じである
領域を区別
–
最短マッチでは困る場合
にも利用
begin(call(* *. criticalSectionStart())) &&
end(call(* *. criticalSectionEnd())) &&
include(call(* *.a())) && include(call(* *.b()))
public void foo() {
…
obj.criticalSectionBegin();
a();
obj.criticalSectionEnd();
…
obj.criticalSectionBegin();
b();
obj.criticalSectionEnd();
…
}
begin(call(* *. criticalSectionStart())) &&
end(call(* *. criticalSectionEnd())) &&
exclude(call(* *.a()))
制御構造と重なる場合

ifやループをまたがって
begin,endが指定された
場合
–

そのままポイントカットする
とプログラムが壊れる
begin,endを両方含む最
小のブロックの中から選
択
public void foo() {
…
if(x.equals(y)){
obj.criticalSectionBegin();
}
obj.criticalSectionEnd();
…
}
begin(call(* *. criticalSectionStart())) &&
end(call(* *. criticalSectionEnd()))
コンテキストの取得

同期処理を行うには対象となるオブジェクトが必
要
–
アドバイス内で元のメソッド内のコンテキストを取得で
きなければならない


レシーバやレシーバのフィールドはthisポイントカットを利用
して取得できる
ローカル変数も取得できなければならない
ローカル変数の取得

変数名での指定はよ
くない
–

ローカル変数名が変
わるとポイントカットで
きなくなる
型で指定
–
args(型,...)を付ける
public void foo() {
SharedObject obj= …;
…
obj.criticalSectionBegin();
…
obj.criticalSectionEnd();
…
}
void around(SharedObject o):
begin(call(* *. criticalSectionStart())) &&
end(call(* *. criticalSectionEnd())) &&
args(o) && args(SharedObject) {
synchronized(o){ proceed(o);}
}
実装

Aspectbench Compiler (abc) に実装
–
–
Jimple(Javaバイトコードの中間言語)の状態でパ
ターンマッチを行う
aroundアドバイスを利用するために工夫
領域外のローカル変数への代入

aroundではjoin point
が別のメソッドに移動
する
–
ローカル変数への代入
が領域の外に伝わらな
い
void joinpoint(String str){
obj.criticalSectionBegin();
str =”bar”;
obj.criticalSectionEnd();
}
public void foo() {
String str= ”foo”;
…
obj.criticalSectionBegin();
advice(str);
str =”bar”;
obj.criticalSectionEnd();
System.out.println(str);//=>foo
…
}
void around():
begin(call(* *. criticalSectionStart())) &&
end(call(* *. criticalSectionEnd()))
{proceed();}
領域外のローカル変数への代入への対処

各ローカル変数をフィー
ルドに格納するオブジェ
クトを作成
–
public void foo() {
String str= ”foo”;
…
$localStore.str=str;
これを切り出されたメソッ
ドの引数として渡す




領域の前でフィールドに
格納
領域の先頭でローカル
変数へ代入
末尾でフィールドに格納
領域の後でローカル変
数へ
別のメソッド
str=$localStore.str;
obj.criticalSectionBegin();
str =”bar”;
obj.criticalSectionEnd();
$localStore.str=str;
str=$localStore.str;
System.out.println(str);//=>bar
…
}
領域の外へのジャンプ

代入と同様に領域外への
ジャンプも行えない
–
return, break, continueな
ど

–
ジャンプ先は複数存在
対処


ジャンプ先毎にIDを割り
当てる
領域の末尾までジャンプ
した後IDを見てジャンプ
public void foo() {…
label: while(…){
別のメソッド
while(…){
obj.criticalSectionBegin();
if(…)
break;
goto
i=0;goto
label0;
endLabel;
else if(…)
goto
i=1;goto
label1;
endLabel;
break
label;
obj.criticalSectionEnd();
endLabel:
if(i==0)goto label0;
if(i==1)goto label1;
} label0:
} label1:
…
}
適用例 (粗い粒度の場合)

Javassistのバグレポートの同期処理の粒度を
切り替える
static WeakHashMap proxyCache;
public Class createClass() {
if (thisClass == null) {
ClassLoader cl = …;
if (useCache)
createClass2(cl);
else
createClass3(cl);
}
return thisClass;}
void around():
begin(call(* *.createClass2(..)))&&
end(call(* *.createClass3(..)))
{
synchronized(
ProxyFactory.proxyCache){
proceed();
}
}
適用例 (細かい粒度の場合)
static WeakHashMap proxyCache;
private void createClass2(ClassLoader cl) {
CacheKey key =…;
HashMap cacheForTheLoader =
(HashMap)proxyCache.get(cl);
if (cacheForTheLoader == null) {
proxyCache.put(...);
}else{...}
Class c = isValidEntry(key);
if (c == null) {
createClass3(cl);
key.proxyClass =
new WeakReference(thisClass);
}else{...} }
void around():
begin(call(* WeakHashMap.get(..)))
&& end(call(* WeakHashMap.put(..))){
synchronized(
ProxyFactory.proxyCache){
proceed();
}}
void around(CacheKey key):
begin(call(* *.isValidEntry(..))) &&
end(call(WeakReference.new(..))) &&
args(CacheKey) && args(key){
synchronized(key){
proceed(key);
}}
実験


Javassistの同期の粒度をアスペクトにより変更
バグレポートに投稿されたマイクロベンチマークを使用して実行時
間を測定(1000回試行)
– 4コア,40スレッド


–
細かい粒度 : 平均実行時間 8.97秒,標準偏差 0.36
粗い粒度 : 平均実行時間 11.94秒,標準偏差 0.17
2コア,40スレッド


細かい粒度 : 平均実行時間 13.31秒,標準偏差 1.02
粗い粒度 : 平均実行時間 13.08秒,標準偏差 0.26
実験環境
CPU:Xeon 3.00GHz 最大4コア
メモリ: 2GB
OS: Linux 2.6.22-14
JVM: java 1.6.0_03
アスペクトを用いて粒度を切り替
えた方が性能が良くなる
粒度の切り替えによる性能の向上
2コア・40スレッドの場合,粗い粒度の方が安定した速度

–
速度の安定性を考えると,粗い粒度の方が良い性能を出す
ことが分かる
細かい粒度(2コア,40スレッド)
粗い粒度(2コア,40スレッド)
700
600
600
500
500
400
400
度数
度数
700
300
300
200
200
100
100
0
0
10~11
11~12
12~13
13~14
14~15
15~16
16~17
実行時間(秒) 平均:13.31秒
17~18
21~22
10~11
11~12
12~13
13~14
14~15
15~16
16~17
実行時間(秒) 平均:13.08秒
17~18
21~22
関連研究

GluonJ/R [熊原ら ’07]
–
–
–

LoopsAJ [Harbulot et al. ’06]
–
–

始点・終点をポイントカットで指定
ブロックの構造を無視したポイントカットを行えてしまう
適用できるアドバイスは例外処理のみ
ループのみポイントカット可能
同一メソッド内の複数のループを区別ができない
synchronized block join point [Xi et al. 08]
–
–
synchronizedブロックをポイントカット
我々の研究は同期処理以外にも応用可能
まとめと今後の課題

領域をポイントカットする機構を提案
–
–
–

同期処理などをアスペクトを用いて分離することがで
きる
AspectBench Compilerを改造して実装
Javassistに適用
今後の課題
–
–
コンテキストの取得方法の改善
より柔軟な領域の指定方法