隨筆 - 4, 文章 - 0, 評(píng)論 - 2, 引用 - 0
          數(shù)據(jù)加載中……

          Groovy深入探索——Call Site分析

          Groovy 1.6引入了Call Site優(yōu)化。Call Site優(yōu)化實(shí)際上就是方法選擇的cache。

          方法選擇
          在靜態(tài)語言(如Java)中,方法調(diào)用的綁定是在編譯期完成的(不完全是這樣,如虛函數(shù),但總的來說,靜態(tài)語言的方法調(diào)用是非常高效的)。而在動(dòng)態(tài)語言(如Groovy)中,調(diào)用的方法是在運(yùn)行時(shí)選擇的。這也是造成動(dòng)態(tài)語言比靜態(tài)語言慢的重要原因之一。
          舉個(gè)例子來說明,譬如要調(diào)用“a.call(1)”。
          如果是Java的話,在編譯期就會(huì)選擇好調(diào)用的方法,在這個(gè)例子中,就是選擇a對(duì)象聲明的類(注意,不是a對(duì)象真正的類,因?yàn)檎嬲念愐竭\(yùn)行時(shí) 才能知道)中,名字為call、有一個(gè)參數(shù)、參數(shù)類型為int的方法(實(shí)際情況要復(fù)雜很多,譬如還要考慮boxing和變參等情況),如果找不到的話則編 譯不通過,否則進(jìn)行方法綁定。反匯編這個(gè)方法調(diào)用的話可以看到如“invokevirtual #4; //Method call:(I)V”的指令,表明方法是綁定好的,包括方法名字“call”,參數(shù)類型“I”(int),返回值“V”(void)。
          如果是Groovy的話,這些都是由Groovy運(yùn)行時(shí)完成的,Groovy對(duì)代碼進(jìn)行編譯時(shí)并不會(huì)檢查到底有沒有一個(gè)方法能匹配這個(gè)調(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)用,譬如:
          = sqr(b);
          = sqr(b);
          是兩個(gè)Call Site。

          Call Site優(yōu)化
          在Groovy 1.6之前,對(duì)于同一個(gè)Call Site來說,調(diào)用該方法n次,則會(huì)進(jìn)行n次的方法選擇,譬如:
          for (i in 1..3) {
              a.call(i)
          }
          這里,Groovy對(duì)call方法就進(jìn)行了3次的選擇,即使3次選擇的結(jié)果都是一樣的。
          Groovy 1.6引入的Call Site優(yōu)化,則是把同一個(gè)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對(duì)這段代碼進(jìn)行編譯,然后再用jad對(duì)編譯后的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" // 每個(gè)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í)際上就是對(duì)$callSiteArray的lazy創(chuàng)建。
          我們可以看到,“acallsite[1].call(a);”就是對(duì)方法名為"a"的CallSite進(jìn)行調(diào)用,而“acallsite[2].callCurrent(this);”則是對(duì)方法名為“d”的CallSite進(jìn)行調(diào)用,如此類推。
          我們?cè)賮砜纯碈allSiteArray的構(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被緩存起來了。
          我們?cè)谶@里只考慮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);
              }
          我們只考慮對(duì)象的metaclass是MetaClassImpl的情況(這也是Groovy對(duì)象的默認(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)建一個(gè)PogoMetaMethodSite,并把找到的方法綁定其中
                  }
                  
          return new PogoMetaClassSite(site, this); //否則創(chuàng)建一個(gè)PogoMetaClassSite
              }
          PogoMetaMethodSite.createPogoMetaMethodSite()就是用來根據(jù)不同的情況創(chuàng)建PogoMetaMethodSite或它的子類的一個(gè)實(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é)這個(gè)過程:
          第一次調(diào)用“acallsite[1].call(a)“時(shí),通過CallSiteArray.createCallSite()方法創(chuàng)建了 PogoMetaMethodSite類的一個(gè)新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()就會(huì)檢查傳入的參數(shù)類型是否與綁定的方法(即上次找到的方法)的參數(shù)類型相同,相同則調(diào)用該綁定的方 法,否則將再次調(diào)用CallSiteArray.createCallSite()方法,創(chuàng)建一個(gè)新的CallSite對(duì)象,并重新進(jìn)行方法選擇。

          除了普通的方法調(diào)用的情況外,還有調(diào)用當(dāng)前對(duì)象方法、獲取/設(shè)置屬性、調(diào)用構(gòu)造函數(shù)、調(diào)用靜態(tài)函數(shù)的情況,在此不再做詳細(xì)分析,有興趣的可以直接查閱Groovy的源代碼。

          以上分析有不當(dāng)之處敬請(qǐng)指出,謝謝大家的閱讀。

          posted on 2009-03-16 09:39 Johnny Jian 閱讀(2373) 評(píng)論(1)  編輯  收藏 所屬分類: Groovy

          評(píng)論

          # re: Groovy深入探索——Call Site分析  回復(fù)  更多評(píng)論   

          寫得很好 :)
          2009-03-16 20:22 | 山風(fēng)小子
          主站蜘蛛池模板: 封丘县| 兴文县| 环江| 土默特右旗| 尼玛县| 吉安县| 子洲县| 襄汾县| 梁平县| 西丰县| 延津县| 衡南县| 钟山县| 神木县| 酒泉市| 吉木乃县| 东乡县| 仙居县| 九台市| 富锦市| 永春县| 扶余县| 尼勒克县| 凤冈县| 大洼县| 盘锦市| 罗城| 孟州市| 扎鲁特旗| 龙井市| 大洼县| 平邑县| 阿克| 龙岩市| 怀化市| 博湖县| 凉城县| 奉化市| 上蔡县| 永靖县| 赫章县|