为什么F#list范围比for循环慢得多?

时间:2012-10-11 14:18:25

标签: list f#

我很惊讶下面示例中列表范围的速度有多慢。在我的机器上,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()

2 个答案:

答案 0 :(得分: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(你的lambda)的实例是新的
  4. ListModule.Iterate遍历列表,为每个元素调用lambda
  5. 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,lambda(它自动内联)或列表创建。正在完成的工作量可能存在显着差异。

    修改

    出于好奇,以下是listseqfor循环版本的FSI时序:

    listIter - Real: 00:00:03.889, CPU: 00:00:04.680, GC gen0: 57, gen1: 51, gen2: 6  
    seqIter  - Real: 00:00:01.340, CPU: 00:00:01.341, GC gen0:  0, gen1:  0, gen2: 0  
    forLoop  - Real: 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))
    

答案 1 :(得分: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;
    }
    }
}