Подтвердить что ты не робот

Агрегат против суммарной производительности в LINQ

Три различные реализации поиска суммы IEnumerable <int> source приведены ниже вместе с временем, когда источник имеет 10 000 целых чисел.

source.Aggregate(0, (result, element) => result + element);  

занимает 3 мс

source.Sum(c => c);

занимает 12 мс

source.Sum();

занимает 1 мс

Мне интересно, почему вторая реализация в четыре раза дороже первой. Разве это не должно быть таким же, как третья реализация.

4b9b3361

Ответ 1

Примечание. Мой компьютер работает .Net 4.5 RC, поэтому возможно, что на мои результаты повлияло это.

Измерение времени, которое требуется для выполнения метода только один раз, обычно не очень полезно. В нем легко доминируют такие вещи, как компиляция JIT, которые не являются актуальными узкими местами в реальном коде. Из-за этого я измерил выполнение каждого метода 100 × (в режиме деблокирования без прикрепленного отладчика). Мои результаты:

  • Aggregate(): 9 мс
  • Sum(lambda): 12 мс
  • Sum(): 6 мс

Тот факт, что Sum() является самым быстрым, не удивительно: он содержит простой цикл без каких-либо делегированных вызовов, что очень быстро. Разница между Sum(lambda) и Aggregate() не так велика, как вы измеряли, но она все еще там. Что может быть причиной этого? Давайте рассмотрим декомпилированный код для двух методов:

public static TAccumulate Aggregate<TSource, TAccumulate>(this IEnumerable<TSource> source, TAccumulate seed, Func<TAccumulate, TSource, TAccumulate> func)
{
    if (source == null)
        throw Error.ArgumentNull("source");
    if (func == null)
        throw Error.ArgumentNull("func");

    TAccumulate local = seed;
    foreach (TSource local2 in source)
        local = func(local, local2);
    return local;
}

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select<TSource, int>(selector).Sum();
}

Как вы можете видеть, Aggregate() использует цикл, но Sum(lambda) использует Select(), который, в свою очередь, использует итератор. И использование итератора означает, что есть некоторые накладные расходы: создание объекта итератора и (возможно, что более важно) еще один вызов метода для каждого элемента.

Убедитесь, что использование Select() на самом деле является причиной, написав наш собственный Sum(lambda) дважды, после использования Select(), который должен вести себя так же, как Sum(lambda) из фреймворка, и один раз без использования Select():

public static int SlowSum<T>(this IEnumerable<T> source, Func<T, int> selector)
{
    return source.Select(selector).Sum();
}

public static int FastSum<T>(this IEnumerable<T> source, Func<T, int> selector)
{
    if (source == null)
        throw new ArgumentNullException("source");
    if (selector == null)
        throw new ArgumentNullException("selector");

    int num = 0;
    foreach (T item in source)
        num += selector(item);
    return num;
}

Мои измерения подтверждают, что я думал:

  • SlowSum(lambda): 12 мс
  • FastSum(lambda): 9 мс