为什么减少比sum或sumBy更快?

时间:2013-08-21 20:36:37

标签: f#

我的同事和我正在比较传递lambda做工作时C#函数的速度,而内联函数与工作时间有关。我们发现您在将lambda投影传递给C#选择函数(例如)时会产生成本,并且想知道F#是否有相同的问题,或者它是否做了不同的事情。

无论我们的初衷是什么,我们偶然发现了一些我们无法弄清楚的事情。在下面的例子中,我们总结了3种不同的方式

  1. 减少
  2. 萨姆
  3. SumBy

  4. 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
    

2 个答案:

答案 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)

sumsumBy使用已检查的算术,但您将未经检查的运算符+传递给reduce - 并非完全不是苹果。