【问题标题】:Is there a better performing functional version of this iterative algorithm in C#?在 C# 中是否有此迭代算法的性能更好的功能版本?
【发布时间】:2012-06-21 16:27:35
【问题描述】:

我希望找到一种方法来以具有扩展功能的功能样式编写以下内容。理想情况下,与迭代/循环版本相比,这种功能风格会表现良好。我猜没有办法。可能是因为许多额外的函数调用和堆栈分配等。

从根本上说,我认为造成麻烦的模式是它既计算一个用于 Predicate 的值,然后又需要该计算值作为结果集合的一部分。

// This is what is passed to each function.
// Do not assume the array is in order.
var a = (0).To(999999).ToArray().Shuffle();

// Approx times in release mode (on my machine):
// Functional is avg 20ms per call
// Iterative is avg 5ms per call
// Linq is avg 14ms per call

private static List<int> Iterative(int[] a)
{
    var squares = new List<int>(a.Length);

    for (int i = 0; i < a.Length; i++)
    {
        var n = a[i];

        if (n % 2 == 0)
        {
            int square = n * n;

            if (square < 1000000)
            {
                squares.Add(square);
            }
        }
    }

    return squares;
}

private static List<int> Functional(int[] a)
{
    return
    a
        .Where(x => x % 2 == 0 && x * x < 1000000)
        .Select(x => x * x)
        .ToList();
}

private static List<int> Linq(int[] a)
{
    var squares =
        from num in a
        where num % 2 == 0 && num * num < 1000000
        select num * num;

    return squares.ToList();
}

【问题讨论】:

  • Iterativen 添加到squares,而不是square。这是故意的吗?
  • 先计算sqrt(1000000)(到一个常数/变量)并在比较中使用它,而不是做n*n。我不会说它会对这里的性能做任何事情,但是在某些情况下可以反转数学运算。 (假设n 是正数。)
  • @dtb 你是对的,我的错。它应该收集方块。这就是本意。

标签: c# functional-programming iteration


【解决方案1】:

康拉德建议的替代方案。这避免了双重计算,但也避免了在不需要时甚至计算平方:

return a.Where(x => x % 2 == 0)
        .Select(x => x * x)
        .Where(square => square < 1000000)
        .ToList();

就我个人而言,在我看到它在更大范围内显着之前,我不会担心性能差异。

(顺便说一下,我假设这只是一个例子。通常你可能会计算一次 1000000 的平方根,然后将 n 与它进行比较,以减少几毫秒。确实如此当然,需要两次比较或 Abs 操作。)

编辑:请注意,功能更强大的版本将完全避免使用ToList。而是返回IEnumerable&lt;int&gt;,让调用者将其转换为List&lt;T&gt;如果他们愿意。如果他们不这样做,他们就不会受到打击。如果他们只想要前 5 个值,他们可以调用Take(5)。根据具体情况,这种懒惰可能在性能上优于原始版本。

【讨论】:

  • 好多了,应该比我的平均速度快 2 倍。
  • 该死的我的意思是收集他的正方形。不过,这个轻微的逻辑问题并没有改变整体思路。我将添加我找到的各种版本的时间。
【解决方案2】:

只是解决你的双重计算问题:

return (from x in a
        let sq = x * x
        where x % 2 == 0 && sq < 1000000
        select sq).ToList();

也就是说,我不确定这是否会大大提高性能。功能变体实际上是否比迭代变体快得多?该代码为自动优化提供了很大的潜力。

【讨论】:

    【解决方案3】:

    一些并行处理怎么样?或者解决方案必须是 LINQ(我认为它很慢)。

    var squares = new List<int>(a.Length);
    
    Parallel.ForEach(a, n =>
    {
      if(n < 1000 && n % 2 == 0) squares.Add(n * n);             
    }
    

    Linq 版本是:

    return a.AsParallel()
      .Where(n => n < 1000 && n % 2 == 0)  
      .Select(n => n * n)
      .ToList();
    

    【讨论】:

      【解决方案4】:

      我认为没有功能性解决方案可以与迭代解决方案在性能方面完全相提并论。在我的时间安排中(见下文),来自 OP 的“功能”实现似乎比迭代实现慢两倍左右。

      像这样的微基准很容易出现各种问题。处理可变性问题的常用策略是重复调用正在计时的方法并计算每次调用的平均时间 - 如下所示:

      // from main
      Time(Functional, "Functional", a);    
      Time(Linq, "Linq", a);    
      Time(Iterative, "Iterative", a);
      // ...
      
      static int reps = 1000;
      private static List<int> Time(Func<int[],List<int>> func, string name, int[] a)
      {
          var sw = System.Diagnostics.Stopwatch.StartNew();
          List<int> ret = null;
          for(int i = 0; i < reps; ++i)
          {
              ret = func(a);
          }
          sw.Stop();
          Console.WriteLine(
              "{0} per call timings - {1} ticks, {2} ms",
              name,
              sw.ElapsedTicks/(double)reps,
              sw.ElapsedMilliseconds/(double)reps);
          return ret;
      }
      

      以下是一节课的时间安排:

      每次通话时间的功能 - 46493.541 滴答,16.945 毫秒 Linq 每次调用时间 - 46526.734 滴答声,16.958 毫秒 每次调用时间迭代 - 21971.274 滴答,8.008 毫秒

      还有许多其他挑战:定时器使用的频闪效应、即时编译器如何以及何时执行其操作、垃圾收集器运行其收集、竞争算法的运行顺序、 cpu 的类型,操作系统交换其他进程的进出等。

      我尝试了一些优化。我从测试中删除了平方 (num * num

      优化(功能)时机:

      每次通话时间优化 - 16849.529 滴答声,6.141 毫秒

      这是按照建议更改的功能实现之一。它按预期输出通过标准的 500 个项目。它看似“更快”只是因为它输出的项目比迭代解决方案少。

      我们可以通过在其实现周围添加一个检查块来使原始实现因溢出异常而崩溃。这是添加到“迭代”方法中的选中块:

      private static List<int> Iterative(int[] a)
      {
          checked
          {
              var squares = new List<int>(a.Length);
      
              // rest of method omitted for brevity...
      
              return squares;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-01-25
        • 1970-01-01
        • 2010-11-27
        • 2021-01-10
        • 2018-02-01
        • 2017-05-10
        • 1970-01-01
        • 1970-01-01
        • 2011-08-17
        相关资源
        最近更新 更多