概述
2021SC@SDUSC
8、Groovy动态方法调用优化分析
问题引入
在学习Groovy中,我们可以知道,Groovy相较于Java是动态语言;在java中,方法的绑定是在编译期完成的,而动态语言的调用方法是在运行时候选择的。
举个例子来说明,譬如要调用“a.call(1)”
。
如果是Java的话,在编译期就会选择好调用的方法,在这个例子中,就是选择a对象声明的类(注意,不是a对象真正的类,因为真正的类要到运行时才能知道)中,名字为call、有一个参数、参数类型为int的方法(实际情况要复杂很多,譬如还要考虑boxing和变参等情况),如果找不到的话则编译不通过,否则进行方法绑定。
如果是Groovy的话,这些都是由Groovy运行时完成的,Groovy对代码进行编译时并不会检查到底有没有一个方法能匹配这个调用。用Groovy 1.5.7进行编译,再反编译为Java代码之后,可以看到如“ScriptBytecodeAdapter.invokeMethodN(class1, a, "call", new Object[] { new Integer(1) })
”的语句,由此看出Groovy在编译时并没有真正的选择调用的方法,而是交由运行时决定。
而我们知道,动态语言的执行速度是要远远少于静态语言的,所有Groovy在1.6版本引入了方法调用优化系统。
优化原理
在Groovy 1.6之前,对于同一个Call Site来说,调用该方法n次,则会进行n次的方法选择,譬如:
for (i in 1..3) {
a.call(i)
}
这里,Groovy对call方法就进行了3次的选择,即使3次选择的结果都是一样的。
Groovy 1.6引入的Call Site优化,则是把同一个Call Site的方法选择结果缓存起来,如果下一次调用时的参数类型一样,则调用该缓存起来的方法,否则重新选择。这就是Call Site优化的基本思想。
优化源码分析
我们可以编写Groovy代码,然后再进行反编译查看源码进行分析。
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对这段代码进行编译,然后再用jad对编译后的class文件进行反编译。因为A类中的方法都是空的,所以我们只看B类的反编译结果。下面是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
}
我们可以看到,里面除了call的执行方法的代码,就是 g e t C a l l S i t e A r r a y getCallSiteArray getCallSiteArray最为关键,我们进去看看 g e t C a l l S i t e A r r a y getCallSiteArray 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;
}
g
e
t
C
a
l
l
S
i
t
e
A
r
r
a
y
(
)
getCallSiteArray()
getCallSiteArray()实际上就是对
c
a
l
l
S
i
t
e
A
r
r
a
y
callSiteArray
callSiteArray的lazy创建。
我们可以看到,“acallsite[1].call(a);
”就是对方法名为"a"的方法进行调用,而“acallsite[2].callCurrent(this);
”则是对方法名为“d”的方法进行调用,如此类推。
我们再来看看CallSiteArray的构造函数里做些什么:
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]);
}
}
所以,第一次调用“acallsite[1].call(a);“时,就是调用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()最后通过调用replaceCallSite()把旧的CallSite替换为新的CallSite,因此第二次调用“acallsite[1].call(a);”时就是直接调用新的CallSite,也就是说该CallSite被缓存起来了。
总结:
最后,我们来再次总结这个过程:
第一次调用“acallsite[1].call(a)“时,通过CallSiteArray.createCallSite()方法创建了新CallSite,并把默认的AbstractCallSite覆盖掉。
当第二次调用“acallsite[1].call(a)“时,会检查传入的参数类型是否与绑定的方法(即上次找到的方法)的参数类型相同,相同则调用该绑定的方法,否则将再次调用CallSiteArray.createCallSite()方法,创建一个新的CallSite对象,并重新进行方法选择。
除了普通的方法调用的情况外,还有调用当前对象方法、获取/设置属性、调用构造函数、调用静态函数的情况,在此不再做详细分析,有兴趣的可以直接查阅Groovy的源代码。
最后
以上就是拼搏花瓣为你收集整理的8、Groovy动态方法调用优化分析的全部内容,希望文章能够帮你解决8、Groovy动态方法调用优化分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复