TGViewer
yet another dev yet another dev @yet_another_dev · 382 subscribers
Post #124 276
Как ошибки в бенчмарке могут привести к неправильным выводам

Видел я однажды пост в LinkedIn, заголовок которого утверждал, что .NET 9 медленнее, чем .NET 8. Сильное заявление. Проверять я его конечно буду. Ведь я сам большой любитель замеров производительности. Перейдём сразу к тому, что не так с бенчмарком.

❌ Методы не возвращают результат

Современные компиляторы умные. Они могут понять, когда выполняемый код не влияет на результат программы, и просто удаляют его. Такая оптимизация называется dead-code elimination (DCE). В данном случае, метод DoSomeThing фактически не делает ничего, поэтому компилятор удаляет его в методе For. Но в методе ForEach_Linq такое сделать нельзя, т.к. в нём создаётся делегат. В результате получается сравнение методов с разным поведением (рисунок 2).

✅ Всегда возвращайте результат из методов

Исправленный вариант может выглядеть так. Нам неважно, что будет в переменной sum. Главное, что её использование предотвращает DCE и не оказывает значительного влияния на результаты бенчмарка.

❌ Слепое сравнивание foreach

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

- foreach с массивом будет преобразован в while c индексатором;
- foreach со списком – в while с List<int>.Enumerator;
- foreach с ICollection<int> – в while с IEnumerator<int>.

✅ Сравнивать производительность foreach нужно с разными типами

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

❌ Передача метода в виде параметра

При передаче метода в виде параметра создаётся делегат. В нашем примере – Action<int> (рисунок 4). Делегат, как известно ссылочный тип, то есть его создание – аллокация памяти, которая влияет на результаты бечмарка.

✅ При передаче метода в виде параметра нужно заранее создать экземпляр делегата

Правильнее было бы проинициализировать делегат в методе GlobalSetup, который выполняется перед бенчмарками.

Выводы

Теперь посмотрим на результаты исправленного бенчмарка. Bad – изначальный бенчмарк, Better – после исправления.

В бенчмарке Bad, который я запустил на своём ноутбуке, метод ForEach_LinqParallel отработал на .NET 9 чуть быстрее, чем на .NET 8. То есть та разница в, о которой говорил автор, была в рамках погрешности.

В бенчмарке Better, метод ForEach_LinqParallel отработал на .NET 9 медленнее чем на .NET 8 на 90 мкс. Такую разницу я бы тоже отнёс к погрешности и не стал акцентировать внимание.

В целом, результаты во всех трёх версиях .NET кажутся плюс-минус одинаковыми. Поэтому, я бы не стал заявлять о том, что .NET 9 медленнее .NET 8.
  • 👍 9
More from @yet_another_dev
  1. Sep 21, 2026Опубликовал вчера ролик в одной запрещённой в России соцсети про то, как сходил на выборы.…
  2. Sep 20, 2026Мы пришли в 7:50 и очередь уже была 🥲 Пообщались с другими людьми. Многие приехали из дру…
  3. Sep 19, 2026Post #383
  4. Sep 18, 2026Последние пару недель на чат нападают боты со спамом (прикрыл стикером). Поэтому чат тепер…
  5. Sep 17, 2026Что интересного в этой статье: 1. Потрачено $120К, а агенты суммарно отработали около 3-х…
  6. Sep 17, 2026В Microsoft переписали рантайм GitHub Copilot с TypeScript на Rust при помощи агентов. Под…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →