7

对于下面的示例,我很惊讶 List 范围有多慢。在我的机器上,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(你的 lambda) 的一个实例是新的
  4. ListModule.Iterate遍历列表,为每个元素调用 lambda

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(它是自动内联的)或列表创建。正在完成的工作量可能存在显着差异。

编辑

出于好奇,以下是listseqforloop 版本的 FSI 时序:

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 回答