Оптимизация производительности в .NET Core 2.1

dotNET  /  Производительность dotNET  /  Performance

Скоро в свет выйдет .NET Core 2.1. Осталось сделать меньше 1% задач. Конечно, оставшиеся 5% проекта занимают 95% времени, но будем оптимистами :)

Далее будет краткое резюме статьи Performance Improvements in .NET Core 2.1.

Если вы готовы прочитать эту интересную, но огромную статью — прочитайте. Я всё-таки пристрастен и расскажу о том, что интересно лично мне. А если не хотите читать и короткую…

TL&DR; Значительно (в 1.5-2 раза) улучшена производительность многих вещей, в том числе:

Далее немного про некоторые оптимизации.

Disclaimer:

  1. Мопед не мой Автор замерял производительность на конкретном железе. Хотя, вряд ли на другом железе будут кардинальные отличия.
  2. Важно понимать, что значительное увеличение производительности по сравнению с 2.0 или на Unix может означать, что раньше было слишком медленно.
  3. Некоторые тесты проверяют производительность минимального набора кода. Прирост производительности сложных приложений, не связанных с большими вычислениями будет не столь впечатляющ.
  4. Если не указано, на какой платформе (Windows/Unix), значит речь, скорее всего, про обе платформы (возможно, где-то я или автор был невнимателен и это про Windows).

Span<T> и Memory<T>

Если вы не в курсе, что такое Span<T> и Memory<T>, рекомендую прочитать статью C# - All About Span: Exploring a New .NET Mainstay.

Упрощенно, Span<T> — это окно для куска памяти (и managed и unmanaged), чтобы не копировать лишний раз. Концептуально — штука хорошая. Если честно, я на это пристально не смотрел и еще не пробовал. Поэтому воздержусь от комментариев.

JIT

В 2.1 добавили оптимизаций, связанных с “девиртуализацией”. Напомню — виртуальный вызов дороже статического. А новые оптимизации иногда позволяют заменить виртуальным вызов статическим. А статический, как вы наверняка помните, иногда можно вообще заинлайнить.

Оптимизировали EqualityComparer<T>.Default — теперь, внутри вызова метода Equals для некоторых типов не происходит виртуальный вызов GenericEqualityComparer<T>.Equals. То есть, если T это Int32, вызов работает быстрее. В 2.5 раза быстрее.

Как следствие, Dictionary<int,int>.ContainsValue работает быстрее более чем в 2 раза.

Boxing

Теперь, если структура реализует интерфейс, то подобное использование не приводит к боксингу:

private void BoxingAllocations(T thing) { if (thing is IAnimal) ((IAnimal)thing).MakeSound(); }

А ещё и девиртуализация происходит.

Понятно, что вызов подобного кода (и только его) в цикле, мягко говоря, не самый распространенный случай… Однако, отсутствие выделения памяти (48 байт) и девятикратное повышение скорости дает шанс неплохой оптимизации и в более жизненных сценариях. В том числе, за счёт этой оптимизации async стал быстрее.

Многопоточность и асинхронность

Строки

В основном, за счет использования Span<T> (строки в большинстве тестов порядка 180 символов):

Форматирование и парсинг

Сеть

За кадром

За кадром остались оптимизации:

dotNET  /  Производительность dotNET  /  Performance