![](https://habrastorage.org/getpro/habr/upload_files/854/47c/10f/85447c10f44c8c4f5579fa6e31a50a51.png)
Исторически так сложилось, что LINQ взыскал сомнительную репутацию за его слабую производительность. LINQ медленный, аллоцирует память, сложно читается, поэтому обычно его используют как инструмент запросов к БД и то, зачастую сложные запросы легче написать на SQL. Даже на собеседованиях джунов просят не использовать LINQ в алгоритмах.
"Я знавал одного разраба, который мог написать запрос абсолютно любой сложности на LINQ, но если надо было что-либо в нём поменять, он писал всё заново" - мой тимлид из EPAM...
Но что если я скажу вам, что с .Net7 всё начнёт меняться. Если вы не были заняты последний год чисткой беклога не поднимая головы, то наверное слышали про новые фичи в .Net6 которые капитально облегчат жизнь разработчикам.
Zip() теперь может совмещать 3 коллекции, ElementAt() теперь может принимать оператор индекса "^" чтобы отсчитывать с конца коллекции, Take() теперь принимает диапазон индексов list.Take(2..6). Также появились новые методы TryGetNonEnumeratedCount(), чтобы не проходиться по коллекции для получения Count, MinBy(), MaxBy(), AvarageBy(), название которых говорит само за себя и новый метод Chunk(), дробящий коллекцию\массив на равные части.
.Net7 же берётся за классические методы и творит с ними чудеса, пока-что особое внимание получили только 4 метода:
.Min()
.Max()
.Average()
.Sum()
И раньше, чтобы выиграть несколько наносекунд на крупных проектах, люди иногда писали собственные методы, что-то на подобии такого
![](https://habrastorage.org/getpro/habr/upload_files/412/bd3/dda/412bd3dda36f3a3229395b16d4523052.png)
И результат был, но не капитальный. Память всё равно выделялась и написание таких методов раздувало проект.
![По массиву из 100 чисел По массиву из 100 чисел](https://habrastorage.org/getpro/habr/upload_files/f9b/8f9/e9c/f9b8f9e9c74e8e726f929d8c96659de5.png)
Теперь посмотрим на бенчмарк по одной дженерик коллекции но в разных версиях фреймворка. Net6 и Net7 RC1, все те методы, получившие изменения.
![Бенчмарк производительности методов в .Net6 и .Net7 RC 1 по одинаковой коллекции. С канала Nick Chapsas. Бенчмарк производительности методов в .Net6 и .Net7 RC 1 по одинаковой коллекции. С канала Nick Chapsas.](https://habrastorage.org/getpro/habr/upload_files/fe5/ea9/57f/fe5ea957ff3ef6eb143a8602640eb8de.png)
Если мы посмотрим на время выполнения этих методов, то глаза начинают перемещаться на лоб, такого прироста обычно вменяемые люди не ожидают, не говоря уже о том, что в данном случае и память не аллоцируется вовсе!
Надо разобраться. Быть того не может.
Берём для примера реализацию Min() из .Net6, в принципе она не менялась давно.
Для тех кто внезапно не видел реализацию этого метода, вот кодище:
![](https://habrastorage.org/getpro/habr/upload_files/6f6/a56/65a/6f6a5665abd5b8aaafe60b9aa1e186ac.png)
Впринципе всё ожидаемо и ясно как белый день, метод расширения с классической реализацией, даже до конца можно не показывать...
А вот когда мы смотрим реализацию на .Net7, начинается интересное. Тут многое по другому.
![В net70 класс Enumerable сначала определяет с каким типом мы работаем. В net70 класс Enumerable сначала определяет с каким типом мы работаем.](https://habrastorage.org/getpro/habr/upload_files/1f2/115/0ad/1f21150adebf4254285800c60b0dd5f9.png)
Мы не сразу переходим в блок кода, мы видим лямбду на метод MinInteger() куда передаётся коллекция, и уже в нём начинается буст производительности.
Это стало возможным поскольку в методы добавили дополнительное условие:
![Если мы не можем получить Span<T> из IEnumerable to включается классическое исполнение метода Если мы не можем получить Span<T> из IEnumerable to включается классическое исполнение метода](https://habrastorage.org/getpro/habr/upload_files/0a5/d02/e15/0a5d02e150d8f8b9a0c2835c88beba10.png)
Сначала мы пытаемся получить из источника структуру Span<T>. Кто не в курсе, что она делает, советую почитать или посмотреть, объяснять её устройство крайне долго, в двух словах - обьект Span представляет непрерывную область памяти и храниться в стеке а не в хипе.
Если же мы не можем получить Span из источника, то всё идёт по старой схеме. Цикл с поэлементным поиском, как и в старых версиях фреймворка.
Если же же да, то условие запускает новый блок кода с векторизацией поиска:
![Если мы можем получить Span<T> то включается векторизация Если мы можем получить Span<T> то включается векторизация](https://habrastorage.org/getpro/habr/upload_files/531/959/738/53195973843b71a975ae034627d9e408.png)
Когда нам удаётся получить Span<T> из источника, запускается поиск с помощью вектора Vector<T> .
![Интерпритация IL. Интерпритация IL.](https://habrastorage.org/getpro/habr/upload_files/989/3a4/e95/9893a4e9575048e11edbbfa5ec33d288.png)
Если начать полностью объяснять процесс векторизации то это займет ещё пару тройку мониторов (Ссылка на полное описание процесса векторизации в конце этой статьи.). Поэтому опишем "грубо говоря":
Дополнительное условие проверяет, является ли длинна битов нашего полученного Span такой же или больше чем Vector с тем же переданным типом, но умноженным на 2. Это позволяет нам искать не по значению, а по длинам кусков битов нашей непрерывной области памяти, и затем сравнивать только значения совпавших длин с нужным результатом. В итоге мы сокращаем поиск в десятки, а может и в сотни раз так как шанс того, что элементы массива или коллекции будут повально занимать такое же пространство, что и наше желаемое значение, крайне мал.
Также условие IsHardwareAccelerated проверяет возможно ли ускорение процесса со стороны железа. Чтобы вернулось true мы должны быть на х64 платформе, а также в Release моде.
![bool зависит от того что скажет JIT компилятор bool зависит от того что скажет JIT компилятор](https://habrastorage.org/getpro/habr/upload_files/c01/98e/d6a/c0198ed6a41657d956228a92d37b2fb3.png)
Однако если даже мы в Debug то уже в рантайме параметр вроде как всё равно измениться на true и векторизация сработает.
Так что вот, Microsoft не сидит на месте и я надеюсь что и в дальнейшем они будут радовать нас увеличением производительности и в других методах/библиотеках. Вместо концовки добавлю только скрин бенча по массиву, чтобы глаз ещё раз порадовался.
![](https://habrastorage.org/getpro/habr/upload_files/523/1af/814/5231af814b2baf55f4e5a9fbf2494367.png)
Почитать про векторизацию можно тут
Структура Vector<T>
Скрин бенчмарка и объяснение работы Span<T> на YouTuve каналe Nick Chapsas
2022. Платон Звонков, начинающий разработчик.