Groovy 1.6引入了Call Site優(yōu)化。Call Site優(yōu)化實(shí)際上就是方法選擇的cache。
方法選擇
在靜態(tài)語言(如Java)中,方法調(diào)用的綁定是在編譯期完成的(不完全是這樣,如虛函數(shù),但總的來說,靜態(tài)語言的方法調(diào)用是非常高效的)。而在動態(tài)語言(如Groovy)中,調(diào)用的方法是在運(yùn)行時(shí)選擇的。這也是造成動態(tài)語言比靜態(tài)語言慢的重要原因之一。
舉個例子來說明,譬如要調(diào)用“a.call(1)”。
如果是Java的話,在編譯期就會選擇好調(diào)用的方法,在這個例子中,就是選擇a對象聲明的類(注意,不是a對象真正的類,因?yàn)檎嬲念愐竭\(yùn)行時(shí)
才能知道)中,名字為call、有一個參數(shù)、參數(shù)類型為int的方法(實(shí)際情況要復(fù)雜很多,譬如還要考慮boxing和變參等情況),如果找不到的話則編
譯不通過,否則進(jìn)行方法綁定。反匯編這個方法調(diào)用的話可以看到如“invokevirtual #4; //Method
call:(I)V”的指令,表明方法是綁定好的,包括方法名字“call”,參數(shù)類型“I”(int),返回值“V”(void)。
如果是Groovy的話,這些都是由Groovy運(yùn)行時(shí)完成的,Groovy對代碼進(jìn)行編譯時(shí)并不會檢查到底有沒有一個方法能匹配這個調(diào)用。用
Groovy
1.5.7進(jìn)行編譯,再反編譯為Java代碼之后,可以看到如
“ScriptBytecodeAdapter.invokeMethodN(class1, a, "call", new Object[] {
new Integer(1) })”的語句,由此看出Groovy在編譯時(shí)并沒有真正的選擇調(diào)用的方法,而是交由運(yùn)行時(shí)決定。
Call Site
根據(jù)wikipedia的定義(
http://en.wikipedia.org/wiki/Call_site),Call Site是一行方法的調(diào)用,譬如:
a = sqr(b);
c = sqr(b);
是兩個Call Site。
Call Site優(yōu)化
在Groovy 1.6之前,對于同一個Call Site來說,調(diào)用該方法n次,則會進(jìn)行n次的方法選擇,譬如:
for (i in 1..3) {
a.call(i)
}
for (i in 1..3) {
a.call(i)
}
這里,Groovy對call方法就進(jìn)行了3次的選擇,即使3次選擇的結(jié)果都是一樣的。
Groovy 1.6引入的Call Site優(yōu)化,則是把同一個Call Site的方法選擇結(jié)果緩存起來,如果下一次調(diào)用時(shí)的參數(shù)類型一樣,則調(diào)用該緩存起來的方法,否則重新選擇。這就是Call Site優(yōu)化的基本思想。
代碼分析
考慮以下的Groovy代碼:
class A {
def a() {}
def b() {}
def b(int i) {}
}
class B {
def a = new A()
def c() {
a.a()
d()
}
def d() {
a.a()
a.b()
a.b(1)
}
}
我們先用Groovy 1.6對這段代碼進(jìn)行編譯,然后再用jad對編譯后的class文件進(jìn)行反編譯。因?yàn)锳類中的方法都是空的,所以我們只看B類的反編譯結(jié)果。下面是B類中的c()和d()方法:
public Object c()
{
CallSite acallsite[] = $getCallSiteArray();
acallsite[1].call(a);
return acallsite[2].callCurrent(this);
}
public Object d()
{
CallSite acallsite[] = $getCallSiteArray();
acallsite[3].call(a);
acallsite[4].call(a);
return acallsite[5].call(a, $const$0); // $const$0就是常量1
}
我們來看看$getCallSiteArray():
private static CallSiteArray $createCallSiteArray()
{
return new CallSiteArray($ownClass, new String[] {
"<$constructor$>", "a", "d", "a", "b", "b" // 每個Call Site的方法名字
});
}
private static CallSite[] $getCallSiteArray()
{
CallSiteArray callsitearray;
if($callSiteArray == null || (callsitearray = (CallSiteArray)$callSiteArray.get()) == null)
{
callsitearray = $createCallSiteArray();
$callSiteArray = new SoftReference(callsitearray);
}
return callsitearray.array;
}
$getCallSiteArray()實(shí)際上就是對$callSiteArray的lazy創(chuàng)建。
我們可以看到,“acallsite[1].call(a);”就是對方法名為"a"的CallSite進(jìn)行調(diào)用,而“acallsite[2].callCurrent(this);”則是對方法名為“d”的CallSite進(jìn)行調(diào)用,如此類推。
我們再來看看CallSiteArray的構(gòu)造函數(shù)里做些什么:
public CallSiteArray(Class owner, String [] names) {
this.owner = owner;
array = new CallSite[names.length];
for (int i = 0; i < array.length; i++) {
array[i] = new AbstractCallSite(this, i, names[i]);
}
}
所以,第一次調(diào)用“acallsite[1].call(a);“時(shí),就是調(diào)用AbstractCallSite類的call方法。下面是該方法的代碼:
public Object call(Object receiver, Object[] args) throws Throwable {
return CallSiteArray.defaultCall(this, receiver, args);
}
再看看CallSiteArray.defaultCall()的代碼:
public static Object defaultCall(CallSite callSite, Object receiver, Object[] args) throws Throwable {
return createCallSite(callSite, receiver, args).call(receiver, args);
}

private static CallSite createCallSite(CallSite callSite, Object receiver, Object[] args) {
CallSite site;
if (receiver == null)
return new NullCallSite(callSite);
if (receiver instanceof Class)
site = createCallStaticSite(callSite, (Class) receiver, args);
else if (receiver instanceof GroovyObject) {
site = createPogoSite(callSite, receiver, args); // 我們只考慮這種情況
} else {
site = createPojoSite(callSite, receiver, args);
}
replaceCallSite(callSite, site); // 替換CallSite
return site;
}
private static void replaceCallSite(CallSite oldSite, CallSite newSite) {
oldSite.getArray().array [oldSite.getIndex()] = newSite;
}
可以看到createCallSite()最后通過調(diào)用replaceCallSite()把舊的CallSite替換為新的CallSite,因此第二次
調(diào)用“acallsite[1].call(a);”時(shí)就是直接調(diào)用新的CallSite,也就是說該CallSite被緩存起來了。
我們在這里只考慮POGO的情況,即createPogoSite()方法。而POJO的情況稍微復(fù)雜一點(diǎn),因?yàn)樯婕暗絇OJO per-instance metaclass的情況(我將在下一篇文章中分析它的實(shí)現(xiàn))。下面是createPogoSite()的代碼:
private static CallSite createPogoSite(CallSite callSite, Object receiver, Object[] args) {
if (receiver instanceof GroovyInterceptable)
return new PogoInterceptableSite(callSite);
MetaClass metaClass = ((GroovyObject)receiver).getMetaClass();
if (metaClass instanceof MetaClassImpl) {
return ((MetaClassImpl)metaClass).createPogoCallSite(callSite, args); // 我們只考慮這種情況
}
return new PogoMetaClassSite(callSite, metaClass);
}
我們只考慮對象的metaclass是MetaClassImpl的情況(這也是Groovy對象的默認(rèn)情況)。下面是MetaClassImpl.createPogoCallSite()的代碼:
public CallSite createPogoCallSite(CallSite site, Object[] args) {
if (site.getUsage().get() == 0 && !(this instanceof AdaptingMetaClass)) {
Class [] params = MetaClassHelper.convertToTypeArray(args); // 獲取參數(shù)的類型
MetaMethod metaMethod = getMethodWithCachingInternal(theClass, site, params); // 選擇方法
if (metaMethod != null)
return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); // 如果找到匹配的方法,則創(chuàng)建一個PogoMetaMethodSite,并把找到的方法綁定其中
}
return new PogoMetaClassSite(site, this); //否則創(chuàng)建一個PogoMetaClassSite
}
PogoMetaMethodSite.createPogoMetaMethodSite()就是用來根據(jù)不同的情況創(chuàng)建PogoMetaMethodSite或它的子類的一個實(shí)例。我們最后來看看PogoMetaMethodSite.call()方法:
public Object call(Object receiver, Object[] args) throws Throwable {
if(checkCall(receiver, args)) { // 如果參數(shù)類型相同,則調(diào)用綁定的方法
try {
return invoke(receiver,args); // 調(diào)用綁定的方法
} catch (GroovyRuntimeException gre) {
throw ScriptBytecodeAdapter.unwrap(gre);
}
} else { // 否則創(chuàng)建新的CallSite,即再次進(jìn)行方法查找
return CallSiteArray.defaultCall(this, receiver, args);
}
}
protected boolean checkCall(Object receiver, Object[] args) {
try {
return usage.get() == 0
&& ((GroovyObject)receiver).getMetaClass() == metaClass // metaClass still be valid
&& MetaClassHelper.sameClasses(params, args); // 檢查參數(shù)類型是否一樣
}
catch (NullPointerException e) {
if (receiver == null)
return false;
throw e;
}
catch (ClassCastException e) {
if (!(receiver instanceof GroovyObject))
return false;
throw e;
}
}
最后,我們來再次總結(jié)這個過程:
第一次調(diào)用“acallsite[1].call(a)“時(shí),通過CallSiteArray.createCallSite()方法創(chuàng)建了
PogoMetaMethodSite類的一個新CallSite,并把默認(rèn)的AbstractCallSite覆蓋掉。在創(chuàng)建
PogoMetaMethodSite的過程中,將進(jìn)行方法的選擇,并把找到的方法綁定到PogoMetaMethodSite中。最后就是調(diào)用該方法:
當(dāng)?shù)诙握{(diào)用“acallsite[1].call(a)“時(shí),就是直接調(diào)用PogoMetaMethodSite.call(),這時(shí)候
PogoMetaMethodSite.call()就會檢查傳入的參數(shù)類型是否與綁定的方法(即上次找到的方法)的參數(shù)類型相同,相同則調(diào)用該綁定的方
法,否則將再次調(diào)用CallSiteArray.createCallSite()方法,創(chuàng)建一個新的CallSite對象,并重新進(jìn)行方法選擇。
除了普通的方法調(diào)用的情況外,還有調(diào)用當(dāng)前對象方法、獲取/設(shè)置屬性、調(diào)用構(gòu)造函數(shù)、調(diào)用靜態(tài)函數(shù)的情況,在此不再做詳細(xì)分析,有興趣的可以直接查閱Groovy的源代碼。
以上分析有不當(dāng)之處敬請指出,謝謝大家的閱讀。