末尾再帰最適化

末尾再帰がちゃんと最適化されるのかどうか気になったので、出来上がった実行ファイルを逆アセンブルして眺めていました。いちごオレを飲みながら。予想通りですが以下のような結果に。(VS2010βまだ入れてないので、例のごとくF#CTP)


書いたコード:

#light

let stopwatch = new System.Diagnostics.Stopwatch()
let gettime (f:Lazy<'a>) =
    stopwatch.Reset() ;
    stopwatch.Start() ; ignore (f.Force()) ; stopwatch.Stop() ;
    stopwatch.Elapsed

(* 通常の斉木さん *)
let rec sum_a = function
    | [] -> 0
    | x :: xs -> x + (sum_a xs)

(* 末尾再帰版 *)
let rec sum_b lst acc =
    match lst with
    | [] -> acc
    | x :: xs -> sum_b xs (x + acc)

(* 畳み込み *)
let sum_c = List.fold_left (+) 0
let sum_d lst = List.fold_right (+) lst 0

let _ =
    let lst = [1..25000]
    let p = printfn "%A ms"
    p <| gettime (lazy (sum_a lst))
    p <| gettime (lazy (sum_b lst 0))
    p <| gettime (lazy (sum_c lst))
    p <| gettime (lazy (sum_d lst))

実行例:

00:00:00.0019770 ms
00:00:00.0005890 ms
00:00:00.0020887 ms
00:00:00.0041452 ms


ビルドして出来上がった実行ファイルを、逆アセンブル
ただ僕はMSIL読めないダメな子なので、それをさらにC#に変換・・・。
すると次のようになります。

// 普通の斉木さん
public static int sum_a(List<int> _arg1)
{
    if (_arg1 is List<int>._Cons)
    {
        List<int> list = ((List<int>._Cons) _arg1).get___Tail();
        return (((List<int>._Cons) _arg1).get___Head() + sum_a(list));
    }
    return 0;
}

// 末尾再帰(ちゃんとループに置き換わっている)
public static int sum_b(List<int> lst, int acc)
{
    while (lst is List<int>._Cons)
    {
        List<int> list = ((List<int>._Cons) lst).get___Tail();
        int num = ((List<int>._Cons) lst).get___Head();
        acc = num + acc;
        lst = list;
    }
    return acc;
}


次にfold(r|l)ですが、これも予想通り展開のされ方が違う。
しかし何が起こっているのか良く分からない・・・汗

// 先にfold_rightから。まあ普通。
public static int sum_d(List<int> lst)
{
    return ListModule.fold_right<int, int>(new clo@109(), lst, 0);
}

// 次にleft. 色々複雑なことになっている
public static FastFunc<List<int>, int> sum_c
{
    get
    {
        return $Program.sum_c@108;
    }
}
// "sum_c@108" って誰? 別の場所を探してみる・・・
// 〜〜
FastFunc<int, FastFunc<int, int>> func = new Program.clo@108();
sum_c@108 = new Program.clo@108_1(func);
// "clo@108"の中にこいつを見つけた
// 〜〜
public override int Invoke(List<int> arg@108_2)
{
    return ListModule.fold_left<int, int>(this.arg@108, 0, arg@108_2);
}


こんな感じ。
FastFuncは名前からして速そうなので、こいつが何かしてる/されてるのかな。
時間もないし面倒なので(蹴)FastFuncの中身までは追っていない。


ということでおしまい!
On Lispで末尾再帰の練習とかしてたから、末尾再帰の関数はあまり違和感なく書けるなぁ(´ー`)