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

发布于 2024-11-29 15:36:39 字数 2333 浏览 0 评论 0原文

我假设具有相同主体的 lambda 函数、委托和匿名函数具有相同的“速度”,但是运行以下简单程序:

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 毫秒

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


更新:

正如评论所建议的,以下内容通过调用 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 毫秒。

I assumed lambda functions, delegates and anonymous functions with the same body would have the same "speed", however, running the following simple program:

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

I get:

Delegate: 4.2948 ms

Lambda: 0.0019 ms

Anonymous: 0.0034 ms

Although negligible, why are these three - apparently identical - methods running at different speeds? What's happening under the hood?


Update:

As suggested by the comments, the following "forces" the Where by calling ToList() on it. In addition, a loop is added to offer more run data:

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

}

The above code results in ~120 ms for each function.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

栀子花开つ 2024-12-06 15:36:40

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

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

正如评论中所指出的,微基准常常具有误导性。有太多你无法控制的因素,JIT 优化、垃圾收集周期等......

请参阅此相关问题:

何时不使用 lambda 表达式

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

Other peoples results suggest that the performance is the same:

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

As noted in the comments, micro-benchmarks are often misleading. There are too many factors over which you have no control, JIT optimisation, garbage collection cycles, etc ...

See this related question:

When not to use lambda expressions

Finally, I think your test is fundamentally flawed! You use a Linq Where extension method to execute your code. However, Linq uses lazy-evaluation, your code will only be executed if you start iterating over the results!

一向肩并 2024-12-06 15:36:40

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% 的差异。

A lambda expression is an anonymous function. "Anonymous function" refers to either a lambda expression or an anonymous method (which is what you've called a "delegate" in your code).

All three operations are using delegates. The second and third are both using lambda expressions. All three will execute in the same way, with the same performance characteristics.

Note that there can be a difference in performance between:

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

and

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

It depends on whether the compiler is able to cache the delegate created by the lambda expression. That will in turn depend on whether it captures variables etc.

For example, consider this code:

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

The compiler is smart, but there's still a difference. Using Reflector, we can see that AllocateInLoop is effectively compiled to:

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

So still only a single delegate instance is created, but there's extra logic within the loop - an extra nullity test on each iteration, basically.

On my machine that makes about a 15% difference in performance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文