隨筆 - 4, 文章 - 0, 評論 - 2, 引用 - 0
          數據加載中……

          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是一行方法的調用,譬如:
          = sqr(b);
          = 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的源代碼。

          以上分析有不當之處敬請指出,謝謝大家的閱讀。

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

          評論

          # re: Groovy深入探索——Call Site分析  回復  更多評論   

          寫得很好 :)
          2009-03-16 20:22 | 山風小子
          主站蜘蛛池模板: 徐闻县| 黄陵县| 镇江市| 班戈县| 洪泽县| 汤原县| 华阴市| 湖口县| 宝应县| 石屏县| 舒兰市| 射阳县| 洱源县| 洛川县| 北海市| 华蓥市| 合水县| 两当县| 玛纳斯县| 延寿县| 孟村| 木里| 广宁县| 呼和浩特市| 武夷山市| 迁西县| 靖西县| 天津市| 吴川市| 江城| 柞水县| 沅江市| 新竹市| 崇明县| 揭西县| 新闻| 方城县| 阿拉尔市| 香河县| 延津县| 惠来县|