【问题标题】:Why is reduce faster than sum or sumBy?为什么 reduce 比 sum 或 sumBy 快?
【发布时间】:2014-05-24 04:10:25
【问题描述】:

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

不管我们最初的意图是什么,我们偶然发现了一些我们无法弄清楚的东西。在以下示例中,我们将列表总结为 3 种不同的方式

  1. 减少
  2. 总和
  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 是这里的大赢家。在反编译中,我可以看到 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;
  }
}

但我很难弄清楚 sum 和 sumBy 在做什么。时间差异来自哪里?


当前的答案表明 reduce 快 5 倍,因为最初我给 reduce 一个未经检查的运算符。但是,更新测试以使用检查运算符(来自 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

【问题讨论】:

  • 我很愿意相信@ildjarn 做对了。检查算术必须减慢速度。
  • @OnorioCatenacci,我用检查算术运行了测试,没有任何区别

标签: f#


【解决方案1】:

Sum 和 SumBy 使用枚举器:

    while e.MoveNext() do
        acc <- Checked.(+) acc e.Current
    acc

而reduce 使用带有优化闭包的递归循环:(reduce 在封面下使用折叠 - fold f head tail)

    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

使用优化的闭包通常可以提高性能。

【讨论】:

  • 嗯,这更有意义,但是,在这种情况下,要点中的 SumLoop 函数应该比 SumEnum 运行得更快。 gist.github.com/faisalmansoor/6301115 但是,List.reduce 归结为 SumLoop 之类的方法,而 List.Sum 归结为 SumEnum。有什么想法吗?
  • 我认为不同的是列表遍历,而不是OptimizedClosure。使用OptimizedClosure 和枚举器几乎不会提高性能,但在递归函数中使用Checked.(+) 与List.Reduce 一样快。
  • 没错,优化的闭包对重度柯里化函数等产生了更大的影响。
【解决方案2】:

sum 和 sumBy 使用检查算术,但您将未经检查的运算符 + 传递给 reduce - 不完全是苹果对苹果。

【讨论】:

  • 嗯,这是有道理的。感谢您的信息
  • 好的,所以我做了一个新的测试,但我认为不是这样。我要更新问题
猜你喜欢
  • 2014-04-18
  • 1970-01-01
  • 1970-01-01
  • 2016-09-28
  • 1970-01-01
  • 1970-01-01
  • 2019-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多