Являются ли лямбда-функции быстрее, чем делегаты / анонимные функции? - PullRequest
11 голосов
/ 13 августа 2011

Я предполагал, что lambda functions, delegates и anonymous 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 мс

Лямбда: 0,0019 мс

Аноним: 0,0034 мс

Хотя эти три, по-видимому, идентичных метода, работают на разных скоростях, хотя и незначительны? Что происходит под капотом?


Обновление:

Как следует из комментариев, следующие "силы" Where вызывают, вызывая ToList(). Кроме того, добавлен цикл для предоставления дополнительных данных прогона:

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 мс для каждой функции.

Ответы [ 2 ]

18 голосов
/ 13 августа 2011

Результаты других людей показывают, что производительность одинакова:

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

Как отмечается в комментариях, микропроцессоры часто вводят в заблуждение.Существует слишком много факторов, которые вы не можете контролировать, JIT-оптимизация, циклы сборки мусора и т. Д.1011 *

Наконец, я думаю, что ваш тест в корне ошибочен! Вы используете метод расширения Linq Where для выполнения своего кода.Однако Linq использует ленивую оценку, ваш код будет выполняться только в том случае, если вы начнете перебирать результаты!

14 голосов
/ 13 августа 2011

Лямбда-выражение является анонимной функцией.«Анонимная функция» относится либо к лямбда-выражению, либо к анонимному методу (это то, что вы назвали «делегатом» в своем коде).

Все три операции используют делегаты.Второй и третий используют лямбда-выражения.Все три будут работать одинаково, с одинаковыми характеристиками производительности.

Обратите внимание, что может иметь разницу в производительности между:

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
}

Это зависит от того, может ли компилятор кэшировать делегат, созданный лямбда-выражением.Это, в свою очередь, будет зависеть от того, захватывает ли он переменные и т. Д.

Например, рассмотрим следующий код:

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%.

...