<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理

    Clojure筆記:用好type hint

    Posted on 2012-07-10 20:37 dennis 閱讀(11952) 評論(1)  編輯  收藏 所屬分類: Clojure

        Clojure的一大優(yōu)點就是跟Java語言的完美配合,Clojure和Java之間可以相互調(diào)用,Clojure可以天然地使用Java平臺上的豐富資源。在Clojure里調(diào)用一個類的方法很簡單,利用dot操作符:

    user=> (.substring "hello" 3)
    "lo"
    user=> (.substring "hello" 0 3)
    "hel"

        上面的例子是在clojure里調(diào)用String的substring方法做字符串截取。Clojure雖然是一門弱類型的語言,但是它的Lisp Reader還是能識別大多數(shù)常見的類型,比如這里hello是一個字符串就可以識別出來,3是一個整數(shù)也可以,通過這些類型信息可以找到最匹配的substring方法,在生成字節(jié)碼的時候避免使用反射,而是直接調(diào)用substring方法(INVOKEVIRTUAL指令)。

        但是當你在函數(shù)里調(diào)用類方法的時候,情況就變了,例如,定義substr函數(shù):
    (defn substr [s begin end] (.substring s begin end))

        我們打開*warn-on-reflection*選項,當有反射的時候告警:

    user=> (set! *warn-on-reflection* true)
    true
    user=> (defn substr [s begin end] (.substring s begin end))
    Reflection warning, NO_SOURCE_PATH:22 - call to substring can't be resolved.
    #'user/substr
       
        問題出現(xiàn)了,由于函數(shù)substr里沒有任何關于參數(shù)s的類型信息,為了調(diào)用s的substring方法,必須使用反射來調(diào)用,clojure編譯器也警告我們調(diào)用substring沒辦法解析,只能通過反射調(diào)用。眾所周知,反射調(diào)用是個相對昂貴的操作(對比于普通的方法調(diào)用有)。這一切都是因為clojure本身是弱類型的語言,對參數(shù)或者返回值你不需要聲明類型而直接使用,Clojure會自動處理類型的轉(zhuǎn)換和調(diào)用。ps.在leiningen里啟用反射警告很簡單,在project.clj里設置:

    ;; Emit warnings on all reflection calls.
      :warn-on-reflection true
       
    過多的反射調(diào)用會影響效率,有沒有辦法避免這種情況呢?有的,Clojure提供了type hint機制,允許我們幫助編譯器來生成更高效的字節(jié)碼。所謂type hint就是給參數(shù)或者返回值添加一個提示:hi,clojure編譯器,這是xxx類型,我想調(diào)用它的yyy方法,請生成最高效的調(diào)用代碼,謝謝合作:
    user=> (defn substr [^String s begin end] (.substring s begin end))
    #'user/substr
         
        這次沒有警告,^String就是參數(shù)s的type hint,提示clojure編譯器說s的類型是字符串,那么clojure編譯器會從java.lang.String類里查找名稱為substring并且接收兩個參數(shù)的方法,并利用invokevirtual指令直接調(diào)用此方法,避免了反射調(diào)用。除了target對象(這里的s)可以添加type hint,方法參數(shù)和返回值也可以添加type hint:
    user=> (defn ^{:tag String} substr [^String s ^Integer begin ^Integer end] (.substring s begin end))
    #'user/substr
        
        返回值添加type hint是利用tag元數(shù)據(jù),提示substr的返回類型是String,其他函數(shù)在使用substr的時候可以利用這個類型信息來避免反射;而參數(shù)的type hint跟target object的type hint一樣以^開頭加上類型,例如這里begin和end都提示說是Integer類型。

        問題1,什么時候應該為參數(shù)添加type hint呢?我的觀點是,在任何為target object添加type hint的地方,都應該相應地為參數(shù)添加type hint,除非你事先不知道參數(shù)的類型。為什么呢?因為clojure查找類方法的順序是這樣:

    1.從String類里查找出所有參數(shù)個數(shù)為2并且名稱為substring方法
    2.遍歷第一步里查找出來的Method,如果你有設置參數(shù)的type hint,則
    查找最匹配參數(shù)類型的Method;否則,如果第一步查找出來的Method就一個,直接使用這個Method,相反就認為沒有找到對應的Method。
    3.如果第二步?jīng)]有找到Method,使用反射調(diào)用;否則根據(jù)該Method元信息生成調(diào)用字節(jié)碼。

       因此,如果substring方法的兩個參數(shù)版本剛好就一個,方法參數(shù)有沒有type hint都沒有關系(有了錯誤的type hint反而促使反射的發(fā)生),我們都會找到這個唯一的方法;但是如果目標方法的有多個重載方法并且參數(shù)相同,而只是參數(shù)類型不同(Java里是允許方法的參數(shù)類型重載的,Clojure只允許函數(shù)的參數(shù)個數(shù)重載),那么如果沒有方法參數(shù)的type hint,Clojure編譯器仍然無法找到合適的調(diào)用方法,而只能通過反射。
       
       看一個例子,定義get-bytes方法調(diào)用String.getBytes:

    user=> (defn get-bytes [s charset] (.getBytes s charset))
    Reflection warning, NO_SOURCE_PATH:26 - call to getBytes can't be resolved.
    #'user/get-bytes
    user=> (defn get-bytes [^String s charset] (.getBytes s charset))
    Reflection warning, NO_SOURCE_PATH:27 - call to getBytes can't be resolved.
    #'user/get-bytes

        第一次定義,s和charset都沒有設置type hint,有反射警告;第二次,s設置了type hint,但是還是有反射警告。原因就在于String.getBytes有兩個重載方法,參數(shù)個數(shù)都是一個,但是接收不同的參數(shù)類型,一個是String的charset名稱,一個Charset對象。如果我們明確地知道這里charset是字符串,那么還可以為charset添加type hint:
    user=> (defn get-bytes [^String s ^String charset] (.getBytes s charset))
    #'user/get-bytes
       
        這次才真正的沒有警告了。總結:在設置type hint的時候,不要只考慮被調(diào)用的target object,也要考慮調(diào)用的方法參數(shù)。

        問題2:什么時候應該添加tag元數(shù)據(jù)呢?理論上,在任何你明確知道返回類型的地方都應該添加tag,但是這不是教條,如果一個偶爾被調(diào)用的方法是無需這樣做的。這一點只對寫庫的童鞋要特別注意。

        Type hint的原理在上文已經(jīng)大概描述了下,具體到clojure源碼級別,請參考clojure.lang.Compiler.InstanceMethodExpr類的構造函數(shù)和emit方法。最后,附送是否使用type hint生成substr函數(shù)的字節(jié)碼之間的差異對比:
    未使用type hint 使用type hint

      // access flags 1

      public invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

       L0

        LINENUMBER 14 L0

       L1

        LINENUMBER 14 L1

        ALOAD 1

        ACONST_NULL

        ASTORE 1

        LDC "substring"

        ICONST_2

        ANEWARRAY java/lang/Object

        DUP

        ICONST_0

        ALOAD 2

        ACONST_NULL

        ASTORE 2

        AASTORE

        DUP

        ICONST_1

        ALOAD 3

        ACONST_NULL

        ASTORE 3

        AASTORE

        INVOKESTATIC clojure/lang/Reflector.invokeInstanceMethod (Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/Object;

       L2

        LOCALVARIABLE this Ljava/lang/Object; L0 L2 0

        LOCALVARIABLE s Ljava/lang/Object; L0 L2 1

        LOCALVARIABLE begin Ljava/lang/Object; L0 L2 2

        LOCALVARIABLE end Ljava/lang/Object; L0 L2 3

        ARETURN

        MAXSTACK = 0

        MAXLOCALS = 0

    public invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;

       L0

        LINENUMBER 15 L0

       L1

        LINENUMBER 15 L1

        ALOAD 1

        ACONST_NULL

        ASTORE 1

        CHECKCAST java/lang/String

        ALOAD 2

        ACONST_NULL

        ASTORE 2

        CHECKCAST java/lang/Number

        INVOKESTATIC clojure/lang/RT.intCast (Ljava/lang/Object;)I

        ALOAD 3

        ACONST_NULL

        ASTORE 3

        CHECKCAST java/lang/Number

        INVOKESTATIC clojure/lang/RT.intCast (Ljava/lang/Object;)I

        INVOKEVIRTUAL java/lang/String.substring (II)Ljava/lang/String;

       L2

        LOCALVARIABLE this Ljava/lang/Object; L0 L2 0

        LOCALVARIABLE s Ljava/lang/Object; L0 L2 1

        LOCALVARIABLE begin Ljava/lang/Object; L0 L2 2

        LOCALVARIABLE end Ljava/lang/Object; L0 L2 3

        ARETURN

        MAXSTACK = 0

        MAXLOCALS = 0


        
        對比很明顯,沒有使用type hint,調(diào)用clojure.lang.Reflector的invokeInstanceMethod方法,使用反射調(diào)用(具體見clojure.lang.Reflector.java),而使用了type hint之后,則直接使用invokevirtual指令(其他方法可能是invokestatic或者invokeinterface等指令)調(diào)用該方法,避免了反射。
          

        參考:

    評論

    # re: Clojure筆記:用好type hint  回復  更多評論   

    2014-02-07 16:26 by xudifsd
    博主研究過core.typed么?貌似它的類型只是為了做靜態(tài)檢查,和生成代碼無關吧?
    主站蜘蛛池模板: 国产一级在线免费观看| 国产自偷亚洲精品页65页| 亚洲午夜久久久精品影院| 免费a级毛片在线观看| 亚洲午夜理论片在线观看| a级毛片免费完整视频| 全免费A级毛片免费看网站| 亚洲色中文字幕在线播放| 国产免费无码一区二区| 成人免费在线视频| 亚洲专区先锋影音| 羞羞的视频在线免费观看| 亚洲精品国产成人影院| 久久99久久成人免费播放| 久久99国产亚洲高清观看首页| 暖暖免费在线中文日本| 亚洲国产精品一区二区第四页| 牛牛在线精品观看免费正 | 中文字幕视频在线免费观看| 免费a级毛片无码a∨蜜芽试看| 日本亚洲免费无线码| 久久精品免费观看国产| 亚洲综合网美国十次| 四虎免费大片aⅴ入口| 亚洲第一区香蕉_国产a| 一级一级一片免费高清| 永久免费毛片手机版在线看| 特色特黄a毛片高清免费观看| 妞干网免费观看视频| eeuss影院免费直达入口| 亚洲精品免费在线观看| 成人免费网站在线观看| 亚洲国产精品张柏芝在线观看 | 亚洲AV无码乱码在线观看牲色 | 三年片在线观看免费观看高清电影| 亚洲AV无码成人精品区狼人影院| 黄页网站在线看免费| 亚洲日韩乱码中文无码蜜桃 | 亚洲精品偷拍视频免费观看| 久久久久亚洲精品日久生情| 日本免费中文字幕|