lambda函数比委托/匿名函数更快吗?

本文关键字:函数 lambda | 更新日期: 2023-09-27 18:05:33

我假设具有相同体的lambda functions, delegatesanonymous 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();
}

:

Delegate: 4.2948 ms

Lambda: 0.0019 ms

Anonymous: 0.0034 ms

虽然可以忽略不计,但为什么这三种看似相同的方法运行速度不同?引擎盖下面发生了什么?


更新:

根据评论的建议,下面通过调用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));
}

上面的代码导致每个函数的运行时间约为120ms。

lambda函数比委托/匿名函数更快吗?

其他人的结果表明,性能是相同的:

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

正如在注释中所指出的,微基准测试常常会误导人。有太多你无法控制的因素,JIT优化,垃圾收集周期,等等。

参见以下相关问题:

何时不使用lambda表达式

最后,我认为你的测试有根本的缺陷!您使用Linq Where扩展方法来执行您的代码。然而,Linq使用延迟求值,你的代码只有在你开始迭代结果时才会被执行!

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);
    }
}

编译器是聪明的,但仍然有区别。使用反射器,我们可以看到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);
}

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

在我的机器上,这会产生大约15%的性能差异。