Почему List <T>.ForEach быстрее стандартного foreach? - PullRequest
2 голосов
/ 13 апреля 2009

Учтите это:

Реквизит:

//The alphabet from a-z
List<char> letterRange = Enumerable.Range('a', 'z' - 'a' + 1)
.Select(i => (Char)i).ToList(); //97 - 122 + 1 = 26 letters/iterations

Стандартный foreach:

foreach (var range in letterRange)
{
    Console.Write(range + ",");
}
Console.Write("\n");

Встроенный foreach:

letterRange.ForEach(range => Console.Write(range + ",")); //delegate(char range) works as well
Console.Write("\n");

Я попытался сопоставить их друг с другом, и встроенный foreach работает в 2 раза быстрее, что выглядит много.

Я погуглил, но не могу найти ответов.

Также, относительно: В .NET какой цикл выполняется быстрее, 'for' или 'foreach'?

for (int i = 0; i < letterRange.Count; i++)
{
    Console.Write(letterRange[i] + ",");
}
Console.Write("\n");

Насколько я могу судить, не действует быстрее, чем стандартный foreach.

Ответы [ 3 ]

17 голосов
/ 13 апреля 2009

Я думаю, что ваш тест неверен. Console.Write - это задача, связанная с вводом / выводом, и это самая трудоемкая часть вашего теста. Это микропроцессор, и его следует проводить очень осторожно для получения точных результатов.

Вот эталонный тест: http://diditwith.net/PermaLink,guid,506c0888-8c5f-40e5-9d39-a09e2ebf3a55.aspx (выглядит хорошо, но я сам не проверял). По состоянию на 8/14/2015 ссылка не работает

10 голосов
/ 13 апреля 2009

Когда вы входите в цикл foreach, вы перечисляете каждый элемент. Это перечисление вызывает два вызова метода на одну итерацию: один к IEnumerator<T>.MoveNext(), а другой к IEnumerator<T>.Current. Это две call инструкции по IL.

List<T>.ForEach быстрее, потому что он имеет только один вызов метода на итерацию - независимо от того, какой у вас предоставлен Action<T> делегат. Это одна callvirt инструкция IL. Это значительно быстрее, чем две call инструкции.

Как отмечали другие, связанные с вводом-выводом инструкции, такие как Console.WriteLine(), будут загрязнять ваш тест. Сделайте что-то, что может быть полностью ограничено памятью, например, добавив вместе элементы последовательности.

2 голосов
/ 13 апреля 2009

Это потому, что метод foreach не использует перечислитель. Перечислители (foreach), как правило, работают медленнее, чем базовый цикл for:

Вот код для метода ForEach:

public void ForEach(Action<T> action)
{
    if (action == null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
    }
    for (int i = 0; i < this._size; i++)
    {
        action(this._items[i]);
    }
}

Хотя я и ожидал, что разница будет, я немного удивлен, что она такая большая, как вы указали. Используя подход перечислителя, вы предпринимаете дополнительное создание объекта, а затем выполняются дополнительные шаги, чтобы убедиться, что перечислитель не признан недействительным (коллекция изменена). Вы также проходите дополнительный вызов функции Current (), чтобы получить члена. Все это добавляет время.

...