`
JohnnyJian
  • 浏览: 106072 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Groovy深入探索——Call Site分析

阅读更多
Groovy 1.6引入了Call Site优化。Call Site优化实际上就是方法选择的cache。

方法选择
在静态语言(如Java)中,方法调用的绑定是在编译期完成的(不完全是这样,如虚函数,但总的来说,静态语言的方法调用是非常高效的)。而在动态语言(如Groovy)中,调用的方法是在运行时选择的。这也是造成动态语言比静态语言慢的重要原因之一。
举个例子来说明,譬如要调用“a.call(1)”。
如果是Java的话,在编译期就会选择好调用的方法,在这个例子中,就是选择a对象声明的类(注意,不是a对象真正的类,因为真正的类要到运行时才能知道)中,名字为call、有一个参数、参数类型为int的方法(实际情况要复杂很多,譬如还要考虑boxing和变参等情况),如果找不到的话则编译不通过,否则进行方法绑定。反汇编这个方法调用的话可以看到如“invokevirtual #4; //Method call:(I)V”的指令,表明方法是绑定好的,包括方法名字“call”,参数类型“I”(int),返回值“V”(void)。
如果是Groovy的话,这些都是由Groovy运行时完成的,Groovy对代码进行编译时并不会检查到底有没有一个方法能匹配这个调用。用Groovy 1.5.7进行编译,再反编译为Java代码之后,可以看到如“ScriptBytecodeAdapter.invokeMethodN(class1, a, "call", new Object[] { new Integer(1) })”的语句,由此看出Groovy在编译时并没有真正的选择调用的方法,而是交由运行时决定。

Call Site
根据wikipedia的定义(http://en.wikipedia.org/wiki/Call_site),Call Site是一行方法的调用,譬如:
a = sqr(b);
c = sqr(b);
是两个Call Site。

Call Site优化
在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代码:
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
    }
我们来看看$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()实际上就是对$callSiteArray的lazy创建。
我们可以看到,“acallsite[1].call(a);”就是对方法名为"a"的CallSite进行调用,而“acallsite[2].callCurrent(this);”则是对方法名为“d”的CallSite进行调用,如此类推。
我们再来看看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被缓存起来了。
我们在这里只考虑POGO的情况,即createPogoSite()方法。而POJO的情况稍微复杂一点,因为涉及到POJO per-instance metaclass的情况(我将在下一篇文章中分析它的实现)。下面是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对象的默认情况)。下面是MetaClassImpl.createPogoCallSite()的代码:
    public CallSite createPogoCallSite(CallSite site, Object[] args) {
        if (site.getUsage().get() == 0 && !(this instanceof AdaptingMetaClass)) {
            Class [] params = MetaClassHelper.convertToTypeArray(args); // 获取参数的类型
            MetaMethod metaMethod = getMethodWithCachingInternal(theClass, site, params); // 选择方法
            if (metaMethod != null)
               return PogoMetaMethodSite.createPogoMetaMethodSite(site, this, metaMethod, params, args); // 如果找到匹配的方法,则创建一个PogoMetaMethodSite,并把找到的方法绑定其中
        }
        return new PogoMetaClassSite(site, this); //否则创建一个PogoMetaClassSite
    }
PogoMetaMethodSite.createPogoMetaMethodSite()就是用来根据不同的情况创建PogoMetaMethodSite或它的子类的一个实例。我们最后来看看PogoMetaMethodSite.call()方法:
    public Object call(Object receiver, Object[] args) throws Throwable {
        if(checkCall(receiver, args)) { // 如果参数类型相同,则调用绑定的方法
            try {
                return invoke(receiver,args); // 调用绑定的方法
            } catch (GroovyRuntimeException gre) {
                throw ScriptBytecodeAdapter.unwrap(gre);
            }
        } else { // 否则创建新的CallSite,即再次进行方法查找
            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); // 检查参数类型是否一样
        }
        catch (NullPointerException e) {
            if (receiver == null)
              return false;
            throw e;
        }
        catch (ClassCastException e) {
            if (!(receiver instanceof GroovyObject))
              return false;
            throw e;
        }
    }

最后,我们来再次总结这个过程:
第一次调用“acallsite[1].call(a)“时,通过CallSiteArray.createCallSite()方法创建了PogoMetaMethodSite类的一个新CallSite,并把默认的AbstractCallSite覆盖掉。在创建PogoMetaMethodSite的过程中,将进行方法的选择,并把找到的方法绑定到PogoMetaMethodSite中。最后就是调用该方法:
当第二次调用“acallsite[1].call(a)“时,就是直接调用PogoMetaMethodSite.call(),这时候PogoMetaMethodSite.call()就会检查传入的参数类型是否与绑定的方法(即上次找到的方法)的参数类型相同,相同则调用该绑定的方法,否则将再次调用CallSiteArray.createCallSite()方法,创建一个新的CallSite对象,并重新进行方法选择。

除了普通的方法调用的情况外,还有调用当前对象方法、获取/设置属性、调用构造函数、调用静态函数的情况,在此不再做详细分析,有兴趣的可以直接查阅Groovy的源代码。

以上分析有不当之处敬请指出,谢谢大家的阅读。
分享到:
评论
1 楼 RednaxelaFX 2009-09-27  
顶起来。之前我只看了Groovy的编译器,没看运行时部分的实现,杯具了啊。
从文中描述看,Groovy的callsite caching是monomorphic inline cache,只记录上一次成功时的调用条件,一旦失败就完全抛弃原有的信息而创建新的callsite。

有一种意见说结构良好的程序在同一个callsite一般也就只会连续用到同一类型的receiver,所以实现polymorphic inline cache是种浪费。不管这种意见正确与否,至少MIC能带来很大的性能提升是事实,而且实现起来也不困难,难怪大家现在都在用……

连CLR在处理接口方法的调用时用的也是MIC,如果MIC不命中的次数太多则退到只用慢速路径。

相关推荐

    Groovy轻松入门——Grails实战基础篇

    ### Groovy轻松入门——Grails实战基础篇 #### 搭建Grails环境及创建Grails Demo程序 **Groovy**是一种面向对象的编程语言,它运行于Java平台上,能够与Java代码无缝集成。而**Grails**则是一款基于Groovy的高性能...

    Groovy need not rails——介绍自己写的一个基于groovy的框架,Webx

    通过深入学习和使用Webx,开发者可以充分利用Groovy的灵活性和生产力优势,同时享受到类似Rails的开发体验,而不必受限于Ruby语言。由于与Java平台的紧密集成,Webx还能够利用丰富的Java生态系统,为大型企业级应用...

    深入探索Groovy脚本:文件操作的艺术

    本文将深入探讨如何使用Groovy脚本进行文件操作,包括文件的创建、读取、写入、删除等基本操作,以及更高级的操作,如文件过滤和搜索。通过实际的代码示例,我们将展示Groovy在文件操作中的优雅和力量。 Groovy提供...

    Show Your ToolBox——锋利的groovy

    《Show Your ToolBox——锋利的Groovy》 在IT领域,工具的选择和使用往往直接影响到工作效率和项目质量。本文将深入探讨Groovy这门强大的动态编程语言,它以其灵活性和与Java的紧密集成,成为了许多开发者的得力...

    scala erlang groovy python 原理 比较 分析

    标题和描述均提到了对五种编程语言——Scala、Erlang、Groovy、Python以及它们各自的原理、比较和分析。这些语言各自拥有独特的特性和应用场景,在编程领域扮演着不同的角色。下面,我们将深入探讨每种语言的关键...

    最新 groovy开发包

    7. **源码分析**:在提供的学习资料中包含源码,这对于深入理解Groovy的运行机制和最佳实践至关重要。通过阅读和分析这些源码,你可以学习到如何设计和实现Groovy类、接口、方法,以及如何利用Groovy的元编程能力。 ...

    apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本

    apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望大家多多下载,apache-groovy-3.0.8.zip apache官网的groovy3.0.8版本,希望...

    Java调用Groovy,实时动态加载数据库groovy脚本

    1. 引入Groovy库:在Java项目中添加Groovy的相关依赖,通常是`groovy-all`,确保Java能够访问Groovy运行时环境。 2. 创建GroovyClassLoader:使用这个类加载器可以动态加载和执行Groovy脚本。它继承自Java的...

    GMock——groovy下的mock工具

    **GMock:Groovy 下的 Mock 工具** 在软件开发中,单元测试是确保代码质量的重要环节。单元测试能够独立地验证代码的各个部分,确保它们按预期工作。为了进行有效的单元测试,经常会用到模拟对象(mock objects),...

    groovy-2.3.6-installer

    Groovy是一种动态、开源的编程语言,它是Java平台上的一个JVM(Java Virtual Machine)语言。Groovy结合了Python、Ruby和Perl等脚本语言的简洁性和灵活性,并且完全兼容Java,可以无缝地与Java代码集成。在"groovy-...

    groovy-sdk-4.0.3

    Groovy SDK 4.0.3 是一个针对Groovy编程语言的软件开发工具包,它包含了Groovy语言的运行环境和开发所需的各种组件。...通过安装和配置这个SDK,你可以开始探索Groovy的动态世界,体验它在各种应用场景中的强大功能。

    groovy入门经典,groovyeclipse 插件

    Groovy是一种动态、灵活的编程语言,它是Java平台上的一个扩展,可以无缝集成到Java项目中。Groovy的语法简洁,支持面向对象编程、函数式编程,并提供了许多现代语言特性,如闭包和动态类型。这使得Groovy成为快速...

    groovy

    本篇文章将深入探讨Groovy语言的关键特性和应用场景。 一、Groovy简介 Groovy是2003年由James Strachan创建的一种开源语言,它是Java虚拟机(JVM)上的一个方言。Groovy代码可以直接与Java代码互操作,因为它们都被...

    Groovy Script 入门

    ### Groovy Script 入门知识点详解 #### 一、Groovy脚本简介 Groovy是一种灵活的面向对象的编程语言,它运行在Java平台上。由于其语法简洁且与Java高度兼容,因此对于Java开发者来说非常容易上手。Groovy不仅支持...

    spring-beans-groovy源码

    Spring框架与Groovy的结合,使得开发者能够利用Groovy的灵活性和便利性来配置Spring Bean,这就是我们今天要探讨的主题——`spring-beans-groovy`模块。 `spring-beans-groovy`是Spring框架的一个组成部分,它允许...

    groovy-3.0.7.msi

    groovy

    apache-groovy-sdk-4.0.1下载

    5. **src**目录:可能包含Groovy语言的源代码,对于想要深入理解Groovy工作原理或者想要参与贡献的开发者非常有用。 6. **bin目录下的脚本**:每个脚本都有相应的Windows批处理文件(.bat)和Unix/Linux shell脚本...

    XStream Deserializable Vulnerablity And Groovy CVE-2015-3253漏洞分析

    本文将深入探讨XStream组件的反序列化漏洞,并结合Groovy的CVE-2015-3253漏洞进行分析,以揭示其中的原理和影响。 XStream是一款流行的Java库,用于XML和对象之间的序列化和反序列化。近期,XStream反序列化漏洞...

Global site tag (gtag.js) - Google Analytics