我的同事和我正在比较传递lambda做工作时C#函数的速度,而内联函数与工作时间有关。我们发现您在将lambda投影传递给C#选择函数(例如)时会产生成本,并且想知道F#是否有相同的问题,或者它是否做了不同的事情。
无论我们的初衷是什么,我们偶然发现了一些我们无法弄清楚的事情。在下面的例子中,我们总结了3种不同的方式
module fs
open NUnit.Framework
open FsUnit
open System
open System.Diagnostics;
[<Test>]
let sumTest() =
let nums = [0..1000]
let repeat = 100000
let stopWatch = new Stopwatch()
stopWatch.Start()
let sumsReduce =
[
for i in [0..repeat] do
yield List.reduce (+) nums
]
Console.WriteLine("reduce = {0} - Time = {1}", List.head sumsReduce, stopWatch.Elapsed.TotalSeconds);
stopWatch.Restart()
let sumsSum =
[
for i in [0..repeat] do
yield List.sum nums
]
Console.WriteLine("sum = {0} - Time = {1}", List.head sumsSum, stopWatch.Elapsed.TotalSeconds);
stopWatch.Restart()
let sumsSumBy =
[
for i in [0..repeat] do
yield List.sumBy id nums
]
Console.WriteLine("sumBy = {0} - Time = {1}", List.head sumsSumBy, stopWatch.Elapsed.TotalSeconds);
stopWatch.Restart()
输出结果如下:
reduce = 500500 - Time = 0.2725156
sum = 500500 - Time = 1.1183165
sumBy = 500500 - Time = 1.1126781
显然,减少是这里的大赢家。在反编译中,我可以看到reduce被归结为
[Serializable]
internal class sumsReduce\u004021\u002D1 : OptimizedClosures.FSharpFunc<int, int, int>
{
internal sumsReduce\u004021\u002D1()
{
base.\u002Ector();
}
public override int Invoke(int x, int y)
{
return x + y;
}
}
但是我很难搞清楚他们正在做什么总和和总和。时间差异在哪里?
目前的答案表明,降低速度要快5倍,因为最初我给予减少未经检查的运算符。但是,更新测试以使用已检查的运算符(来自Checked
模块),我仍然得到相同的结果
let sumsReduce =
[
for i in [0..repeat] do
yield List.reduce (Checked.(+)) nums
]
请注意时间差异仍然存在
reduce = 500500 - Time = 0.274697
sum = 500500 - Time = 1.1126796
sumBy = 500500 - Time = 1.1370642
答案 0 :(得分:17)
Sum和SumBy使用枚举器:
while e.MoveNext() do
acc <- Checked.(+) acc e.Current
acc
而reduce使用带有优化闭包的递归循环:(减少使用覆盖下的折叠 - 折叠f头尾)
let fold<'T,'State> f (s:'State) (list: 'T list) =
match list with
| [] -> s
| _ ->
let f = OptimizedClosures.FSharpFunc<_,_,_>.Adapt(f)
let rec loop s xs =
match xs with
| [] -> s
| h::t -> loop (f.Invoke(s,h)) t
loop s list
使用优化的闭包通常可以提高性能。
答案 1 :(得分:7)
sum
和sumBy
使用已检查的算术,但您将未经检查的运算符+
传递给reduce
- 并非完全不是苹果。