【问题标题】:Are lambda functions faster than delegates/anonymous functions?lambda 函数是否比委托/匿名函数更快?
【发布时间】:2011-08-13 05:48:01
【问题描述】:

我假设lambda functionsdelegatesanonymous functions 具有相同的主体将具有相同的“速度”,但是,运行以下简单程序:

static void Main(string[] args)
{
    List<int> items = new List<int>();

    Random random = new Random();

    for (int i = 0; i < 10000000; i++)
    {
        items.Add(random.Next());
    }

    Stopwatch watch;
    IEnumerable<int> result;

    Func<int, bool> @delegate = delegate(int i)
    {
        return i < 500;
    };
    watch = Stopwatch.StartNew();
    result = items.Where(@delegate);
    watch.Stop();
    Console.WriteLine("Delegate: {0}", watch.Elapsed.TotalMilliseconds);

    Func<int, bool> lambda = i => i < 500;
    watch = Stopwatch.StartNew();
    result = items.Where(lambda);
    watch.Stop();
    Console.WriteLine("Lambda: {0}", watch.Elapsed.TotalMilliseconds);

    watch = Stopwatch.StartNew();
    result = items.Where(i => i < 500);
    watch.Stop();
    Console.WriteLine("Inline: {0}", watch.Elapsed.TotalMilliseconds);

    Console.ReadLine();
}

我明白了:

委托:4.2948 毫秒

Lambda:0.0019 毫秒

匿名:0.0034 毫秒

虽然可以忽略不计,但为什么这三种(显然相同)的方法以不同的速度运行?幕后发生了什么?


更新:

根据 cmets 的建议,以下通过调用 ToList() 来“强制”Where。此外,还添加了一个循环以提供更多的运行数据:

while (true) 
{
    List<int> items = new List<int>();

    Random random = new Random();

    for (int i = 0; i < 10000000; i++)
    {
        items.Add(random.Next());
    }

    Stopwatch watch;
    IEnumerable<int> result;

    Func<int, bool> @delegate = delegate(int i)
    {
        return i < 500;
    };
    watch = Stopwatch.StartNew();
    result = items.Where(@delegate).ToList();
    watch.Stop();
    Console.WriteLine("Delegate: {0}", watch.Elapsed.TotalMilliseconds);

    Func<int, bool> lambda = i => i < 500;
    watch = Stopwatch.StartNew();
    result = items.Where(lambda).ToList();
    watch.Stop();
    Console.WriteLine("Lambda: {0}", watch.Elapsed.TotalMilliseconds);

    watch = Stopwatch.StartNew();
    result = items.Where(i => i < 500).ToList();
    watch.Stop();
    Console.WriteLine("Inline: {0}", watch.Elapsed.TotalMilliseconds);
    Console.WriteLine(new string('-', 12));

}

上面的代码导致每个函数大约需要 120 毫秒。

【问题讨论】:

  • 只是出于好奇,您是否尝试过以不同的顺序运行这 3 个?
  • 添加预热期并运行所有测试几个周期(全部大循环)并强制在哪里。在这些更改之后,我没有发现任何明显的差异。
  • 这段代码中@的作用是什么? (@delegate)
  • @Omar:您显示的代码肯定不会花费 120 毫秒 - 我建议您编辑代码以显示您如何“强制”在哪里。跨度>
  • @Mikael:谢谢 - 不知道你能做到。

标签: c# delegates anonymous-function lambda


【解决方案1】:

其他人的结果表明性能是一样的:

http://blogs.microsoft.co.il/blogs/alex_golesh/archive/2007/12/11/anonymous-delegates-vs-lambda-expressions-vs-function-calls-performance.aspx

正如 cmets 中所述,微基准通常具有误导性。有太多你无法控制的因素,JIT 优化,垃圾回收周期等等......

查看这个相关问题:

When not to use lambda expressions

最后,我认为您的测试存在根本缺陷!您使用 Linq Where 扩展方法来执行您的代码。但是,Linq 使用惰性求值,只有在开始迭代结果时才会执行您的代码!

【讨论】:

  • 你能把最后一段加粗吗? :)
【解决方案2】:

lambda 表达式一个匿名函数。 “匿名函数”指的是 lambda 表达式或匿名方法(在您的代码中将其称为“委托”)。

所有三个操作都使用委托。第二个和第三个都使用 lambda 表达式。这三个都将以相同的方式执行,具有相同的性能特征。

请注意,可能在以下性能方面存在差异:

Func<int, int> func = x => ...;
for (int i = 0; i < 10000; i++) {
    CallFunc(func);
}

for (int i = 0; i < 10000; i++) {
    CallFunc(x => ...) // Same lambda as before
}

这取决于编译器是否能够缓存 lambda 表达式创建的委托。这又取决于它是否捕获变量等。

例如,考虑以下代码:

using System;
using System.Diagnostics;

class Test
{
    const int Iterations = 1000000000;

    static void Main()
    {
        AllocateOnce();
        AllocateInLoop();
    }

    static void AllocateOnce()
    {
        int x = 10;

        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        Func<int, int> allocateOnce = y => y + x;
        for (int i = 0; i < Iterations; i++)
        {
            sum += Apply(i, allocateOnce);
        }
        sw.Stop();
        Console.WriteLine("Allocated once: {0}ms", sw.ElapsedMilliseconds);
    }

    static void AllocateInLoop()
    {
        int x = 10;

        Stopwatch sw = Stopwatch.StartNew();
        int sum = 0;
        for (int i = 0; i < Iterations; i++)
        {
            sum += Apply(i, y => y + x);
        }
        sw.Stop();
        Console.WriteLine("Allocated in loop: {0}ms", sw.ElapsedMilliseconds);
    }

    static int Apply(int loopCounter, Func<int, int> func)
    {
        return func(loopCounter);
    }
}

编译器很聪明,但还是有区别的。使用 Reflector,我们可以看到 AllocateInLoop 被有效地编译为:

private static void AllocateInLoop()
{
    Func<int, int> func = null;
    int x = 10;
    Stopwatch stopwatch = Stopwatch.StartNew();
    int sum = 0;
    for (int i = 0; i < Iterations; i++)
    {
        if (func == null)
        {
            func = y => y + x;
        }
        sum += Apply(i, func);
    }
    stopwatch.Stop();
    Console.WriteLine("Allocated in loop: {0}ms", stopwatch.ElapsedMilliseconds);
}

所以仍然只创建了一个委托实例,但循环中有额外的逻辑 - 基本上每次迭代都有一个额外的无效测试。

在我的机器上,性能大约有 15% 的差异。

【讨论】:

  • 我不确定你是如何检查 IL 输出的,但我认为 delegate { Foo(); }() =&gt; { Foo(); } 会编译成相同的 IL 对吧?
  • @LeaHayes:我希望如此,是的。