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

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

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

    莊周夢蝶

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

    Clojure的recur尾遞歸優化探秘

    Posted on 2010-07-11 04:20 dennis 閱讀(4263) 評論(0)  編輯  收藏 所屬分類: javaClojure

        Clojure由于是基于JVM,同樣無法支持完全的尾遞歸優化(TCO),這主要是Java的安全模型決定的,可以看看這個久遠的bug描述。但是Clojure和Scala一樣支持同一個函數的直接調用的尾遞歸優化,也就是同一個函數在函數體的最后調用自身,會優化成循環語句。讓我們看看這是怎么實現的。
        Clojure的recur的特殊形式(special form)就是用于支持這個優化,讓我們看一個例子,經典的求斐波那契數:
    (defn recur-fibo [n]
         (letfn [(fib 
                    [current next n]
                    (
    if (zero? n)
                        current
                        ;recur將遞歸調用fib函數
                        (recur next (
    + current next) (dec n))))]
        (fib 
    0 1 n)))

        recur-fibo這個函數的內部定義了一個fib函數,fib函數的實現就是斐波那契數的定義,fib函數的三個參數分別是當前的斐波那契數(current)、下一個斐波那契數(next)、計數器(n),當計數器為0的時候返回當前的斐波那契數字,否則就將當前的斐波那契數設置為下一個,下一個斐波那契數字等于兩者之和,計數遞減并遞歸調用fib函數。注意,你這里不能直接調用(fib next (+ current next) (dec n)),否則仍將棧溢出。這跟Scala不同,Clojure是用recur關鍵字而非原函數名作TOC優化。

        Clojure是利用asm 3.0作字節碼生成,觀察下recur-fibo生成的字節碼會發現它其實生成了兩個類,類似user$recur_fibo__4346$fib__4348和user$recur_fibo__4346,user是namespace,前一個是recur-fibo中的fib函數的實現,后一個則是recur-fibo自身,這兩個類都繼承自 clojure.lang.AFunction類,值得一提的是前一個類是后一個類的內部類,這跟函數定義相吻合。所有的用戶定義的函數都將繼承 clojure.lang.AFunction。
       
        在這兩個類中都有一個invoke方法,用于實際的方法執行,讓我們看看內部類fib的invoke方法(忽略了一些旁枝末節)
     1 // access flags 1
     2   public invoke(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; throws java/lang/Exception 
     3    L0
     4     LINENUMBER 2 L0
     5    L1
     6     LINENUMBER 4 L1
     7    L2
     8     LINENUMBER 4 L2
     9     ALOAD 3
    10     INVOKESTATIC clojure/lang/Numbers.isZero (Ljava/lang/Object;)Z

    11     IFEQ L3
    12     ALOAD 1
    13     GOTO L4

    14    L5
    15     POP
    16    L3
    17     ALOAD 2
    18    L6
    19     LINENUMBER 6 L6
    20     ALOAD 1
    21     ALOAD 2
    22     INVOKESTATIC clojure/lang/Numbers.add (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Number;

    23    L7
    24     LINENUMBER 6 L7
    25     ALOAD 3
    26     INVOKESTATIC clojure/lang/Numbers.dec (Ljava/lang/Object;)Ljava/lang/Number;

    27     ASTORE 3
    28     ASTORE 2
    29     ASTORE 1
    30     GOTO L0

    31    L4
    32    L8
    33     LOCALVARIABLE this Ljava/lang/Object; L0 L8 0
    34     LOCALVARIABLE current Ljava/lang/Object; L0 L8 1
    35     LOCALVARIABLE next Ljava/lang/Object; L0 L8 2
    36     LOCALVARIABLE n Ljava/lang/Object; L0 L8 3
    37     ARETURN
    38     MAXSTACK = 0
    39     MAXLOCALS = 0

        首先看方法簽名,invoke接收三個參數,都是Object類型,對應fib函數里的current、next和n。
        關鍵指令都已經加亮,9——11行,加載n這個參數,利用Number.isZero判斷n是否為0,如果為0,將1彈入堆,否則彈入0。IFEQ比較棧頂是否為0,為0(也就是n不為0)就跳轉到L3,否則繼續執行(n為0,加載參數1,也就是current,然后跳轉到L4,最后通過ARETURN返回值current作結果。
       
        指令20——22行,加載current和next,執行相加操作,生成下一個斐波那契數。
        指令25-——26行,加載n并遞減。
        指令27——29行,將本次計算的結果存儲到local變量區,覆蓋了原有的值。
        指令30行,跳轉到L0,重新開始執行fib函數,此時local變量區的參數值已經是上一次執行的結果。
       
        有的朋友可能要問,為什么加載current是用aload 1,而不是aload 0,處在0位置上的是什么?0位置上存儲的就是著名的this指針,invoke是實例方法,第一個參數一定是this。

       從上面的分析可以看到,recur干的事情就兩件:覆蓋原有的local變量,以及跳轉到函數開頭執行循環操作,這就是所謂的軟尾遞歸優化。這從RecurExp的實現也可以看出來:

       //覆蓋變量
      for (int i = loopLocals.count() - 1; i >= 0; i--) {
                    LocalBinding lb 
    = (LocalBinding) loopLocals.nth(i);
                    Class primc 
    = lb.getPrimitiveType();
                    
    if (primc != null) {
                        gen.visitVarInsn(Type.getType(primc).getOpcode(Opcodes.ISTORE), lb.idx);
                    }
                    
    else {
                        gen.visitVarInsn(OBJECT_TYPE.getOpcode(Opcodes.ISTORE), lb.idx);
                    }
                }
       
    //執行跳轉
       gen.goTo(loopLabel);
     
          recur分析完了,最后有興趣可以看下recur-fibo的invoke字節碼
     1  L0
     2     LINENUMBER 1 L0
     3     ACONST_NULL
     4     ASTORE 2
     5     NEW user$recur_fibo__4346$fib__4348
     6     DUP
     7     INVOKESPECIAL user$recur_fibo__4346$fib__4348.<init> ()V

     8     ASTORE 2
     9     ALOAD 2
    10     CHECKCAST user$recur_fibo__4346$fib__4348
    11     POP
    12    L1
    13    L2
    14     LINENUMBER 7 L2
    15     ALOAD 2
    16     CHECKCAST clojure/lang/IFn
    17     GETSTATIC user$recur_fibo__4346.const__2 : Ljava/lang/Object;
    18     GETSTATIC user$recur_fibo__4346.const__3 : Ljava/lang/Object;
    19     ALOAD 1
    20     ACONST_NULL
    21     ASTORE 1
    22     ACONST_NULL
    23     ASTORE 2
    24     INVOKEINTERFACE clojure/lang/IFn.invoke (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
    25    L3
    26     LOCALVARIABLE fib Ljava/lang/Object; L1 L3 2
    27    L4
    28     LOCALVARIABLE this Ljava/lang/Object; L0 L4 0
    29     LOCALVARIABLE n Ljava/lang/Object; L0 L4 1
    30     ARETURN

         5——7行,實例化一個內部的fib函數。
         24行,調用fib對象的invoke方法,傳入3個初始參數。

         簡單來說,recur-fibo生成的對象里只是new了一個fib生成的對象,然后調用它的invoke方法,這也揭示了Clojure的內部函數的實現機制。
          

    主站蜘蛛池模板: 久久99精品免费视频| 国产精品免费大片| 性xxxx视频播放免费| 亚洲av日韩av综合| 永久免费的网站在线观看| 亚洲AV色吊丝无码| 毛片基地免费视频a| 春暖花开亚洲性无区一区二区| 女人18毛片水真多免费看| 亚洲熟妇AV一区二区三区浪潮| 黑人粗长大战亚洲女2021国产精品成人免费视频 | 国产亚洲av片在线观看播放| 国产在线播放线91免费| 国产成人亚洲精品青草天美| 免费观看成人久久网免费观看| 亚洲网址在线观看你懂的| 成年人免费的视频| 亚洲无mate20pro麻豆| 成全视频免费高清| 日韩在线视频免费| 亚洲AV永久无码精品| 青娱乐免费视频在线观看| 最新亚洲春色Av无码专区| 国产v片免费播放| 中文字幕在线视频免费| 亚洲首页在线观看| 女人18毛片a级毛片免费视频| 色婷婷精品免费视频| 亚洲乱码国产乱码精品精| 最近中文字幕电影大全免费版| 亚洲看片无码在线视频| 亚洲国产a级视频| 91大神免费观看| 国产午夜亚洲精品不卡免下载| 在线观看亚洲成人| jjizz全部免费看片| 免费大片av手机看片高清| 亚洲四虎永久在线播放| 在线观看亚洲免费| 免费一级毛片无毒不卡| 亚洲日韩国产一区二区三区在线|