Почему делает второе для цикла, всегда выполняются быстрее, чем первый?

Я использую EF 4 и EF 6 в двух разных проектах в одном решении.

После использования NuGet для EF 6.0 все ссылки на EF 4 были удалены в моем тестовом проекте. Мне пришлось снова удалить EF6 из проекта и добавить ссылки на System.Data.Entity для EF4.

Для доступа к данным через EF6 в тестовом проекте мне пришлось вручную ссылаться на EntityFramework.SqlServer.dll (4.5) и использовать рекомендацию по взлому (см. Выше).

Нет необходимости делать какие-либо изменения в app.config моего тестового проекта.

нет

 <section name="entityFramework" ...

и нет

<entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" /> ...
6
задан Nosredna 20 June 2009 в 15:01
поделиться

8 ответов

Вероятно потому, что классы (например, консоль) необходимо JIT-компилировать с первого раза. Вы получите наилучшие показатели, вызвав сначала все методы (для JIT (разогрев, затем)), а затем выполнив тест.

Как указали другие пользователи, четырех проходов никогда не будет достаточно, чтобы показать вам

Между прочим, разница в производительности между for и foreach будет незначительной, а преимущества удобочитаемости использования foreach почти всегда перевешивают любое незначительное улучшение производительности.

16
ответ дан 8 December 2019 в 02:41
поделиться
  1. I не будет использовать DateTime для измерения производительности - попробуйте класс Секундомер .
  2. Измерение всего за 4 прохода никогда не даст хорошего результата. Лучше использовать> 100 000 проходов (можно использовать внешний цикл). Не используйте Console.WriteLine в своем цикле.
  3. Еще лучше: используйте профилировщик (например, Redgate ANTS или NProf)
7
ответ дан 8 December 2019 в 02:41
поделиться

Я просто проводил тесты, чтобы получить реальные числа, но тем временем Газ опередил меня в ответе - вызов Console.Writeline отключается при первом вызове, так что вы оплатить эту стоимость в первом цикле.

Просто для информации - используя секундомер, а не дату и время и измеряя количество тактов:

Без вызова Console.Writeline перед первым циклом время было

for: 16802
foreach: 2282

с вызов Console.Writeline, они были

for: 2729
foreach: 2268

Хотя эти результаты не всегда повторялись из-за ограниченного числа прогонов, но величина различия всегда была примерно одинаковой.


Отредактированный код для справки:

        int[] x = new int[] { 3, 6, 9, 12 };
        int[] y = new int[] { 3, 6, 9, 12 };

        Console.WriteLine("Hello World");

        Stopwatch sw = new Stopwatch();

        sw.Start();
        for (int i = 0; i < 4; i++)
        {
            Console.WriteLine(x[i]);
        }
        sw.Stop();
        long elapsedTime = sw.ElapsedTicks;

        sw.Reset();
        sw.Start();
        foreach (var item in y)
        {
            Console.WriteLine(item);
        }
        sw.Stop();
        long elapsedTime2 = sw.ElapsedTicks;

        Console.WriteLine("\nSummary");
        Console.WriteLine("--------------------------\n");
        Console.WriteLine("for:\t{0}\nforeach:\t{1}", elapsedTime, elapsedTime2);

        Console.ReadKey();
3
ответ дан 8 December 2019 в 02:41
поделиться

Я не очень разбираюсь в C #, но когда я правильно помню, Microsoft создавала компиляторы «Just in Time» для Java. Когда они используют те же или похожие методы в C #, было бы довольно естественно, что «некоторые конструкции, идущие вторыми, работают быстрее».

Например, может быть, что JIT-система видит, что цикл выполняется, и решает, что скомпилировать весь метод. Следовательно, когда достигается второй цикл, он еще скомпилирован и работает намного быстрее, чем первый. Но это мое довольно упрощенное предположение. Конечно, вам нужно гораздо лучше разбираться в системе времени выполнения C #, чтобы понять, что происходит. Также может быть, что в первом цикле обращение к RAM-странице осуществляется первым, а во втором она все еще находится в кэше ЦП.

Дополнение: Другой комментарий, который был сделан: То, что модуль вывода может быть JIT-преобразован в первый раз в первом цикле, кажется мне более вероятным, чем мое первое предположение. Современные языки просто очень сложны, чтобы узнать, что происходит под капотом. Также это мое утверждение вписывается в это предположение:

Но также у вас есть терминальные выходы в ваших циклах. Они еще больше усложняют задачу. Также может быть, что для первого открытия терминала в программе требуется некоторое время.

3
ответ дан 8 December 2019 в 02:41
поделиться

Причина, по которой в версии foreach есть несколько форм накладных расходов, которых нет в цикле for

  • Использование IDisposable.
  • Дополнительный вызов метода для каждого элемента. Доступ к каждому элементу должен осуществляться изнутри с помощью IEnumerator .Current , который является вызовом метода. Поскольку он находится в интерфейсе, он не может быть встроен. Это означает, что N вызовов методов, где N - количество элементов в перечислении. Цикл for просто использует и индексатор
  • В цикле foreach все вызовы проходят через интерфейс. В общем, это немного медленнее, чем через конкретный тип

. Обратите внимание, что перечисленные мной вещи не обязательно являются огромными затратами. Обычно это очень небольшие затраты, которые могут способствовать небольшой разнице в производительности.

Также обратите внимание, как указал Мердад, компиляторы и JIT могут выбрать оптимизацию цикла foreach для определенных известных структур данных, таких как массив. Конечным результатом может быть просто цикл for.

Примечание. Для большей точности вашего теста производительности требуется немного больше доработки.

  • Вы должны использовать секундомер вместо DateTime. Это намного точнее для тестов производительности.
  • Вы должны выполнять тест много раз, а не один раз
  • Вам нужно выполнить фиктивный прогон в каждом цикле, чтобы устранить проблемы, возникающие при JIT-запуске метода в первый раз. Вероятно, это не проблема, если весь код находится в одном методе, но это не повредит.
  • Вам нужно использовать более 4 значений в списке. Вместо этого попробуйте 40 000.
2
ответ дан 8 December 2019 в 02:41
поделиться

Вы должны использовать секундомер для измерения времени поведения.

Технически цикл for работает быстрее. Foreach вызывает метод MoveNext () (создание стека методов и другие накладные расходы из вызова) на итераторе IEnumerable, когда для требуется только увеличить переменную.

1
ответ дан 8 December 2019 в 02:41
поделиться

Я не понимаю, почему здесь все говорят, что для будет быстрее, чем foreach в данном конкретном случае. Для List он (примерно в 2 раза медленнее до foreach через список, чем до для через List ]).

Фактически, foreach здесь будет немного быстрее , чем для . Поскольку foreach в массиве по существу компилируется в:

for(int i = 0; i < array.Length; i++) { }

Использование .Length в качестве критерия остановки позволяет JIT удалять проверки границ при доступе к массиву, поскольку это особый случай. Использование i <4 заставляет JIT вставлять дополнительные инструкции для проверки каждой итерации, выходит ли i за пределы массива, и выбросить исключение, если это так. Однако с .Length он может гарантировать, что вы никогда не выйдете за пределы массива, поэтому проверки границ избыточны, что ускоряет работу.

Однако в большинстве циклов накладные расходы на цикл незначительны по сравнению с работой, выполняемой внутри.

Я полагаю, что наблюдаемое вами несоответствие можно объяснить только JIT.

1
ответ дан 8 December 2019 в 02:41
поделиться

Я бы не стал вдаваться в подробности - это не лучший код профилирования по следующим причинам
1. DateTime не предназначен для профилирования. Вы должны использовать QueryPerformanceCounter или StopWatch, которые используют счетчики профиля оборудования CPU
2. Console.WriteLine - это метод устройства, поэтому могут возникнуть тонкие эффекты, такие как буферизация, которые необходимо учитывать
3. Выполнение одной итерации каждого блока кода никогда не даст вам точных результатов, потому что ваш ЦП выполняет много фанковых оптимизаций на лету, таких как выполнение вне очереди и планирование инструкций
4. Скорее всего, код, который подвергается JIT-обработке для обоих блоков кода, довольно похож, поэтому он, вероятно, находится в кэше инструкций для второго блока кода

Чтобы получить лучшее представление о времени, я сделал следующее

  1. Заменил Console.WriteLine с математическим выражением (e ^ num)
  2. Я использовал QueryPerformanceCounter / QueryPerformanceTimer через P / Invoke
  3. Я запускал каждый блок кода 1 миллион раз, а затем усреднил результаты

Когда я это сделал, я получил следующее результаты:

Цикл for занял 0,000676 миллисекунд
Цикл foreach занял 0,000653 миллисекунды

Таким образом, foreach был немного быстрее, но ненамного

Затем я провел несколько дополнительных экспериментов и сначала запустил блок foreach, а второй блок for
Когда я это сделал, я получил следующие результаты:

Цикл foreach занял 0,000702 миллисекунды
Цикл for занял 0,000691 миллисекунды

Наконец, я дважды запустил оба цикла вместе, то есть для + foreach, затем снова for + foreach
Когда я это сделал, я получил следующие результаты:

Цикл foreach занял 0,00140 миллисекунд
Цикл for занял 0,001385 миллисекунды

Так что в основном мне кажется, что любой код, который вы запускаете вторым, выполняется немного быстрее, но не достаточно, чтобы иметь какое-либо значение.
- Редактировать -
Вот пара полезных ссылок
Как синхронизировать управляемый код с помощью QueryPerformanceCounter
Кэш инструкций
Выполнение вне очереди

1
ответ дан 8 December 2019 в 02:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: