Кирилл @teoadal
Senior .NET Developer
Information
- Rating
- Does not participate
- Location
- Нижний Новгород, Нижегородская обл., Россия
- Registered
- Activity
Specialization
Backend Developer, Fullstack Developer
Lead
SQL
C#
ASP.NET MVC
Linq
.NET
ASP.Net
PostgreSQL
Там точно ошибка.
В бенчмарке For есть надпись i < Items.Length. Items в данном бенчмарке это статическое свойство, которое вычисляется при обращении к нему. То есть при каждой итерации по циклу мы каждый раз дергаем свойство Items где каждый раз заново создается массив у которого берется Length.
Отсюда такая бешенная аллокация.
Отмечу ещё, что автор статьи не бежит по массиву, а просто инкрементирует i и приплюсовывает результат к sum. То есть он не обращается к содержимому массива вообще. В деле поиска подстроки это поможет вряд ли, так как строку для разделения всё-таки надо читать.
Спасибо вам больше! Мне было очень интересно. Жаль, что статья не получила продолжения!
Если они сделают как обещали (оптимизация по чтению), то я уверен, что будет быстрее. Но мы, конечно, перепроверим после выхода net8.
Foreach не генерит мусор, если Enumerator это struct. Вроде как struct enumerator'ы реализованы уже для всех основных коллекций.
Если же вы бежите по IList или другим интерфейсам коллекций, то да, struct enumerator будет упакован и будет аллокация.
Про Linq согласен. Linq в "горячем месте кода" надо разворачивать в foreach.
Да, для 99% случаев я в обычном коде использую ConcurrentDictionary. Это сильно проще, чем городить высокопроизводительный код, который трудно поддерживать.
ReadWriteLockSlim я тоже использую. С ним, правда, есть нюанс. Если будет очень много читателей, то записывающий код будет ждать очень очень долго, прежде чем ему будет позволено записать что-то.
Там же не очередь, насколько я помню. Где-то в видео от коллег из JetBrains было, что они столкнулись с подобным, и им пришлось написать свой ReadWriteLockSlim, но с очередью.
Ого! Спасибо, я попробую. С SIMD обращусь к специалисту, у меня с ним нет опыта.
Давайте говорить предметно.
1. О каком коде мы говорим? Давайте вы изложите его в виде того самого кода, который у вас в голове. Решение должно быть быстрее ConcurrentDictionary и потреблять меньше памяти, подтвердите это бенчмарками.
2. Потом мы его обсудим.
3. Потом мы его сравним с тем способом, который я не помню.
4. Убедимся, что это не тот способ.
5. Согласимся, что ваш способ ошибочен и так поступать не нужно.
И никто, заметьте, с этой умной мыслью не спорит. Наоборот, в самом конце статьи я про это и пишу - надо быть крайне осторожным. Код, который показывает хорошие бенчмарки, но не работает под нужным профилем нагрузки... ну он как бы не решает задачу, не рабочий.
Коллега, спор походит на лингвистический, с попытками толкования, основываясь на собственном мнении и без приведения аргументов.
Вы к чему ведёте-то в рамках темы?
Да, знакомая ситуация. И, увы, нет, я не знаю никаких способов это победить. Приходится ставить восклицательные знаки там, где я точно знаю, что не null.
Мне кажется, что вы неправы. Моё мнение основывается на следующих шагах:
1. Берём любимый поисковик, вбиваем: "C# thread-safe mutex".
2. Убеждаемся, что mutex множество раз употребляется в контексте потокобезопасности.
3. Идём на github в код mutex'a. Вот сюда, например.
4. В открывшемся файле находим ключевое слово
unsafe
.Вывод, который я делаю: слово "небезопасный" употребляется в случае потокобезопасности и не означает "неработающий".
Коллега, странный спор, так как я не понимаю, к чему вы ведёте и какую мысль отстаиваете.
Отвечу по существу:
1. Я написал "вроде как". Я помню, что там был какой-то трюк, но вспомнил только lock-free.
2. Я разве писал про то, что код не будет работать в каких-то случаях? Я написал только про то, что мне помнится, что он был сомнительный и небезопасный. Есть разница между словом "небезопасный" и "неработающий".
3. Мне кажется, что у вас в голове сформировалось какая-то реализация, и вы предположили, что я имею ввиду именно эту реализацию. Почему? Я же чётко написал, что я её даже не помню.
Я так сказал?
Мне кажется, что видел что-то вроде вот этого из RavenDB: своя имплементация lock-free dictionary.
Для повышения производительности или уменьшения аллокации там, где это нужно. Например, в библиотечном коде или коде таких проектов как RavenDB.
Я начну комментировать с конца вашего сообщения, так как я полностью согласен с этим утверждением. Вообще, заниматься производительностью это грабли, боль и унижение. Тем не менее, есть 1% случаев, когда это действительно нужно.
Нет, мы экономим на каждом вызове методов словаря. И это только в контексте нашего обсуждения по call/callvirt. См. бенчмарки.
Мне кажется, что вы рассуждаете с позиции, что для 99% разработчиков одна аллокация погоды не сделает. И это правда. Но есть случаи, когда просто необходима высокая производительность и насколько можно низкая аллокация. Даже за счёт экономии на спичках.
Раньше я в самом начале своих статей писал более длинное и более развернутое предупреждение о том, что подобные эксперименты - только для специалистов и только для узкого круга случаев применения. В этот раз я ограничился фразой: 99% программистов этого не нужно, а подобные эксперименты без изучения environment'a будут даже опасны.
Я ещё раз скажу, что заниматься производительностью это грабли, боль и унижение. Прежде чем использовать подобные штуки в проде надо подумать миллион раз, потом миллион раз перепроверить, да ещё и тесты с бенчмарками написать. В этом случае вероятность ошибиться есть, но она крайне низка.
Мы, вроде, с вами читаем одни и те же вещи, но понимаем их по разному. Я ещё раз напомню, что моё утверждение состоит в следующем: использовать структуры для увеличения производительности это хороший способ избежать callvirt.
Вот я взял и создал класс Glossary, скопировал в него код структуры, запечатал. Создал такой же класс, но не запечатывал его. Смотрим измерений производительности. Как и предсказывалось: структура быстрее, за счёт того, что есть call, а не callvirt.
Соглашусь, слово "всегда" слишком сильное. В 80% случаев, кроме некоторых - вот тут относительно недавно обсуждалось.
Я хотел напомнить всё это лишь для того, чтобы объяснить почему я сказал относительно структур "runtime'у не надо будет выяснять по таблице виртуальных методов, кому принадлежит этот метод". Детально вдаваться в объяснения и пояснения, по которым люди пишут длинные посты и даже статьи я не намеревался.
Возможно, я выразился не очень точно. Я говорил об IL-коде, где вызов статического метода это
call
, вызов не статического метода - всегдаcallvirt
(даже если метод не помечен ключевым словомvirtual
).Вызов методов структур похож на статические методы, там тоже
call
, который несколько быстрее, чемcallvirt
.Прочитать можно вот тут и тут.
Про call и callvirt можно тоже прочитать.
Цель подобных трюков как раз в том, чтобы обмануть компилятор и попытаться заинлайнить метод. Вообще, все микрооптимизации не про красивый код, а про то, чтоб добиться производительности.
Однако, в данном конкретном случае я в версию инлайна верю слабо. Скорее тут это сделано для единообразия кода.
Спасибо большое! Как дойдут руки, я обязательно добавлю ваш код в статью.