Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/list/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
List 为什么F#列表范围比循环慢得多?_List_F# - Fatal编程技术网

List 为什么F#列表范围比循环慢得多?

List 为什么F#列表范围比循环慢得多?,list,f#,List,F#,对于下面的例子,我很惊讶列表范围慢了很多。在我的机器上,for循环快8倍左右 是否首先创建了10000000个元素的实际列表?如果是这样的话,是否有一个原因(除了它还没有被完成)使得编译器不能优化它 open System open System.Diagnostics let timeFunction f v = let sw = Stopwatch.StartNew() let result = f v sw.ElapsedMilliseconds let len

对于下面的例子,我很惊讶列表范围慢了很多。在我的机器上,for循环快8倍左右

是否首先创建了10000000个元素的实际列表?如果是这样的话,是否有一个原因(除了它还没有被完成)使得编译器不能优化它

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()

使用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)
            )
        )
    );
}
…无
IEnumerable
、lambda(自动内联)或列表创建。在完成的工作量上存在潜在的显著差异

编辑

出于好奇,以下是
list
seq
循环版本的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
使用
{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;
    }
    }
}

另一部分原因是,使用for循环,生成的代码简单、快速,并且具有良好的数据局部性。这个范围的代码有很多跳转/分支,这给循环的每个步骤都增加了很多延迟。创建列表涉及许多非本地堆分配。相比之下,跳转/分支可能是一个非常小的问题。您正在创建一个列表。实际的清单。在运行时。这涉及到许多分配。循环不分配任何内容。使用
{0..length}|>Seq.iter
可能会更幸运,因为它不会分配任何内容。它仍然会慢一些,但不会慢很多。
seq
快得多,但仍然比
forLoop
慢2.5倍。我在答案中添加了计时。我不知道
forinlist
怎么会比
forLoop
更快;它编译成完全相同的东西。我得到的时间略有不同,但可能只是因为JIT。您所说的代码在“for..in”的情况下是相同的。但是,每次运行这些函数时,我都会得到大致相同的结果。顺序可能会对结果产生一些影响。列表编号:4457 forLoop:586 arrayiter:1893序列编号:1803 forinlist:567 reclist:569
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;
    }
    }
}