7

以下の例では、リストの範囲がどれだけ遅いかに驚いています。私のマシンでは、for ループは 8 倍ほど高速です。

10,000,000 要素の実際のリストが最初に作成されますか? もしそうなら、なぜこれがコンパイラによって最適化されないのか (まだ行われていないこと以外に) 理由はありますか?

open System
open System.Diagnostics

let timeFunction f v =
    let sw = Stopwatch.StartNew()
    let result = f v
    sw.ElapsedMilliseconds

let length = 10000000

let doSomething n =
    (float n) ** 0.1 |> ignore

let listIter n =
    [1..length] |> List.iter (fun x -> doSomething (x+n))

let forLoop n = 
    for x = 1 to length do
        doSomething (x+n)

printf "listIter   : %d\n" (timeFunction listIter 1)  // c50
GC.Collect()
printf "forLoop    : %d\n" (timeFunction forLoop 1)  // c1000
GC.Collect()
4

2 に答える 2

9

ILSpyを使用すると、listIter次のようになります。

public static void listIter(int n)
{
    ListModule.Iterate<int>(
        new listIter@17(n), 
        SeqModule.ToList<int>(
            Operators.CreateSequence<int>(
                Operators.OperatorIntrinsics.RangeInt32(1, 1, 10000000)
            )
        )
    );
}

関連する基本的な手順は次のとおりです。

  1. RangeInt32を作成しますIEnumerable(これは不可解にラップされCreateSequenceます)
  2. SeqModule.ToListそのシーケンスからリストを作成します
  3. listIter@17(あなたのラムダ)のインスタンスは新しくなりました
  4. ListModule.Iterate各要素のラムダを呼び出すリストをトラバースします

vs forLoop、これはあなたが書いたものとそれほど変わらないように見えます:

public static void forLoop(int n)
{
    for (int x = 1; x < 10000001; x++)
    {
        int num = x + n;
        double num2 = Math.Pow((double)num, 0.1);
    }
}

...いいえIEnumerable、ラムダ(自動的にインライン化されます)、またはリストの作成。実行される作業の量には、潜在的に大きな違いがあります。

編集

好奇心のために、、、、およびループバージョンのFSIタイミングはlistseqforとおりです。

listIter-実数:00:00:03.889、CPU:00:00:04.680、GC gen0:57、gen1:51、gen2:6  
seqIter-実数:00:00:01.340、CPU:00:00:01.341、GC gen0:0、gen1:0、gen2:0  
forLoop-実数:00:00:00.565、CPU:00:00:00.561、GC gen0:0、gen1:0、gen2:0

およびseq参照用のバージョン:

let seqIter n =
    {1..length} |> Seq.iter (fun x -> doSomething (x+n))
于 2012-10-11T14:40:25.267 に答える
3

{1..length} |> Seq.iter メモリ内に完全なリストを作成しないため、使用する方が確実に高速です。

for ループよりも少し速い別の方法は次のとおりです。

let reclist n =
    let rec downrec x n =
        match x with 
        | 0 -> ()
        | x -> doSomething (x+n); downrec (x-1) n
    downrec length n

興味深いことに、再帰関数のコードは次のようになります。

while (true)
{
    switch (x)
    {
    case 0:
        return;
    default:
    {
        int num = x + n;
        double num2 = Math.Pow((double)num, 0.1);
        int arg_26_0 = x - 1;
        n = n;
        x = arg_26_0;
        break;
    }
    }
}

最適化を使用している場合でも、削除できる行がいくつかあります。つまり、次のようになります。

while (true)
{
    switch (x)
    {
    case 0:
        return;
    default:
    {
        int num = x + n;
        double num2 = Math.Pow((double)num, 0.1);
        x = x - 1;
        break;
    }
    }
}
于 2012-10-11T20:12:46.680 に答える