List 为什么F#列表范围比循环慢得多?
对于下面的例子,我很惊讶列表范围慢了很多。在我的机器上,for循环快8倍左右 是否首先创建了10000000个元素的实际列表?如果是这样的话,是否有一个原因(除了它还没有被完成)使得编译器不能优化它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
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;
}
}
}