Где находится эффективный код #философия
737. Кажется, назрел вопрос - а кто, я, собственно, такой.
1. Привет, я - Кирилл.
2. Я программист, как и вы.
3. У меня есть бзик - производительность.
4. При этом, я не фанатик, который хочет заоптимизировать всё подряд. Я не тот, кто давит на окружающих - оптимизируй! Я имею возможность, но не делаю этого.
5. Я тот, кто хочет перевести код из состояния "не оптимальный код" в "оптимальный код" (см. рисунок - точки А в B). Максимум, дойти до "оптимизированный код" (С).
6. Но не для всех случаев. Часто, я бенчмаркаю ради того, чтобы исследовать возможности .NET.
7. Я стараюсь блюсти тему канала. Я не душню, я просто держусь темы. Мне кажется это важно, т.к. каналов про общие знания о .NET десятки.
8. Также, я понимаю бизнес. Я пять лет как lead с разными размерами команды. И да, я понимаю, что знания о производительности - дорогие знания, не нужные многим программистам, которые просто хотят заставить эту железку работать.
Я лишь хочу, чтобы мы заставляли эту железку работать оптимально.
737. Кажется, назрел вопрос - а кто, я, собственно, такой.
1. Привет, я - Кирилл.
2. Я программист, как и вы.
3. У меня есть бзик - производительность.
4. При этом, я не фанатик, который хочет заоптимизировать всё подряд. Я не тот, кто давит на окружающих - оптимизируй! Я имею возможность, но не делаю этого.
5. Я тот, кто хочет перевести код из состояния "не оптимальный код" в "оптимальный код" (см. рисунок - точки А в B). Максимум, дойти до "оптимизированный код" (С).
6. Но не для всех случаев. Часто, я бенчмаркаю ради того, чтобы исследовать возможности .NET.
7. Я стараюсь блюсти тему канала. Я не душню, я просто держусь темы. Мне кажется это важно, т.к. каналов про общие знания о .NET десятки.
8. Также, я понимаю бизнес. Я пять лет как lead с разными размерами команды. И да, я понимаю, что знания о производительности - дорогие знания, не нужные многим программистам, которые просто хотят заставить эту железку работать.
Я лишь хочу, чтобы мы заставляли эту железку работать оптимально.
Автор картинки выше - некий Алексей Шипилёв, хорошо известный в узких кругах Java-программистов. Картинка из доклада "Перформанс: что в имени тебе моём?", подстрочник которого находится тут.
Всем, кто так или иначе интересуется производительностью, я рекомендую просмотр и вдумчивое чтение. Это, так сказать, философия в облике фактов о Java. Одинаково применимая, кстати, к любым технологиям. В том числе к .NET.
Жаль, что в .NET нет таких людей. Некий Андрей Акиньшин мог бы, но ушёл в высокую статистику. Очень жаль. Его ранние доклады и заметки были очень классными. Посмотрите видосики. Особенно интересы замечания конферансье - "хорошй специалист, на самом самом, там, низком уровне".
#философия
Всем, кто так или иначе интересуется производительностью, я рекомендую просмотр и вдумчивое чтение. Это, так сказать, философия в облике фактов о Java. Одинаково применимая, кстати, к любым технологиям. В том числе к .NET.
Жаль, что в .NET нет таких людей. Некий Андрей Акиньшин мог бы, но ушёл в высокую статистику. Очень жаль. Его ранние доклады и заметки были очень классными. Посмотрите видосики. Особенно интересы замечания конферансье - "хорошй специалист, на самом самом, там, низком уровне".
#философия
YouTube
Алексей Шипилёв — Перформанс: Что В Имени Тебе Моём?
Подробнее о Java-конференциях:
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
. . . . Оптимизация производительности бередит умы опытных разработчиков с начала компьютерных времён. В коллективном бессознательном оптимизация…
— весной — JPoint: https://jrg.su/gTrwHx
— осенью — Joker: https://jrg.su/h7yvG4
— —
. . . . Оптимизация производительности бередит умы опытных разработчиков с начала компьютерных времён. В коллективном бессознательном оптимизация…
Преждевременная пессимизация #философия #игра
Хотелось бы напомнить лекцию некого Дмитрия Иванова - "Сказки о преждевременной оптимизации". Лекция далёкого 2015 года. Коллега из компании, где производительность является ключевой фичей, напоминает нам о том, что мантра "Преждевременная оптимизация - корень всех зол" не всегда верна.
Например, мы исходим из предположения, что мы всегда найдём те 1-20% кода, которые тормозят всё и, конечно, поправим. Увы, нет. Иногда получается так, что 90% кода тормозят и мы вынуждены переписывать продукт почти заново.
Далеко ведь ходить не надо. Вот пример Cities Skylines 2. Вроде игрушечка, вроде могли всё написать нормально сразу. Но нет. Почему-то надо было выпуститься к определённой дате. Наверное, маркетологи и бизнес что-то знали и это, наверное, правильно. Увы, мне кажется, что они ошиблись - куча негативных отзывов тому пример.
Эта история ещё раз подтверждает тезис Дмитрия - "преждевременная пессимизация - корень зол". Т.е. если разработчики вообще не уделяют внимания элементарными подходам к производительности, то потребуется много человекочасов и масса изменений в коде, чтобы сделать всё более-менее оптимально. Это те же деньги, это то же время.
У меня в этой связи вопрос: понимает ли бизнес и донёс ли технический лидер эту простую мысль до руководства?
Хотелось бы напомнить лекцию некого Дмитрия Иванова - "Сказки о преждевременной оптимизации". Лекция далёкого 2015 года. Коллега из компании, где производительность является ключевой фичей, напоминает нам о том, что мантра "Преждевременная оптимизация - корень всех зол" не всегда верна.
Например, мы исходим из предположения, что мы всегда найдём те 1-20% кода, которые тормозят всё и, конечно, поправим. Увы, нет. Иногда получается так, что 90% кода тормозят и мы вынуждены переписывать продукт почти заново.
Далеко ведь ходить не надо. Вот пример Cities Skylines 2. Вроде игрушечка, вроде могли всё написать нормально сразу. Но нет. Почему-то надо было выпуститься к определённой дате. Наверное, маркетологи и бизнес что-то знали и это, наверное, правильно. Увы, мне кажется, что они ошиблись - куча негативных отзывов тому пример.
Эта история ещё раз подтверждает тезис Дмитрия - "преждевременная пессимизация - корень зол". Т.е. если разработчики вообще не уделяют внимания элементарными подходам к производительности, то потребуется много человекочасов и масса изменений в коде, чтобы сделать всё более-менее оптимально. Это те же деньги, это то же время.
У меня в этой связи вопрос: понимает ли бизнес и донёс ли технический лидер эту простую мысль до руководства?
YouTube
Дмитрий Иванов — Сказки о преждевременной оптимизации
Подробнее о конференции DotNext: https://jrg.su/3WmFRE
— —
Дмитрий Иванов, JetBrains — Сказки о преждевременной оптимизации.
Конференция DotNext 2015 Piter, Санкт-Петербург, 05.06.2015.
Правилом «premature optimization is the root of all evil» руководствуется…
— —
Дмитрий Иванов, JetBrains — Сказки о преждевременной оптимизации.
Конференция DotNext 2015 Piter, Санкт-Петербург, 05.06.2015.
Правилом «premature optimization is the root of all evil» руководствуется…
Кто тут по поводу создания игр?
Хочу дать хороший ECS - DefaultECS. Как мне кажется, это интересная имплементация паттерна. Да, можно быстрее и лучше (см. LeoECS), но этот чувак как минимум реализовал
Кстати, пользуясь случаем, хочу задать несколько вопросов по поводу ECS вообще их конкретных имплементаций. Я, знаете ли, иногда тоже балуюсь - вдруг получится реализовать лучше, чем решения коллег выше.
1. Так ли нужно создавать мультимиры? Это вопрос тестирования или чего-то ещё?
2. Там и сям у Entity есть версия. Зачем? Сделайте её
3. Так ли нужно держать массив по количеству энтитей в каждом из ComponentPool? Конечно, это сильно быстрее. Но блин, если у меня 512 компонентов и 2 000 000 сущностей... Ну, например, карта 512 на 512 с юнитами.
4. Почему вы не делите данные на сущности игры, сущности настроек (ассеты?) и сущности изображений? Ну типа Actor/Asset/View?
5. Ну и, вопрос из кровавого энтерпайза, почему нет более-менее адекватной связи с подобиями MediatR? Это ж просто. И это отлично ложится на ECS.
#игра #решение
Хочу дать хороший ECS - DefaultECS. Как мне кажется, это интересная имплементация паттерна. Да, можно быстрее и лучше (см. LeoECS), но этот чувак как минимум реализовал
DebuggerTypeProxy
, а значит хотя бы пытался работать со своим решением. Кстати, пользуясь случаем, хочу задать несколько вопросов по поводу ECS вообще их конкретных имплементаций. Я, знаете ли, иногда тоже балуюсь - вдруг получится реализовать лучше, чем решения коллег выше.
1. Так ли нужно создавать мультимиры? Это вопрос тестирования или чего-то ещё?
2. Там и сям у Entity есть версия. Зачем? Сделайте её
ref struct
и она никогда не будет хранится отдельно от контекста. Вопрос сравнения энтитей по версии стоит вообще?3. Так ли нужно держать массив по количеству энтитей в каждом из ComponentPool? Конечно, это сильно быстрее. Но блин, если у меня 512 компонентов и 2 000 000 сущностей... Ну, например, карта 512 на 512 с юнитами.
4. Почему вы не делите данные на сущности игры, сущности настроек (ассеты?) и сущности изображений? Ну типа Actor/Asset/View?
5. Ну и, вопрос из кровавого энтерпайза, почему нет более-менее адекватной связи с подобиями MediatR? Это ж просто. И это отлично ложится на ECS.
#игра #решение
Бенч, который не работал #бенч #память #скорость
Однажды ко мне пришёл подпрыгивающий от изумления коллега, и показал примерно вот такой бенчмарк. По его замерам выходило, что
Ответ на эту загадку, конечно, элементарный, но показательный. Кто-то знает, в чём проблема?
—
Так как прошло достаточно времени, то можно написать правильный ответ прямо тут: дело в том, что при вызове
Вывод: проблему сложно заметить, она не про перформанс, а про внимательность. Поэтому надо быть очень внимательным, когда мы пишем код, ведь нам очень легко получить проблему с производительностью на пустом месте. Вот как тут.
Код бенчмарка в комментариях.
Однажды ко мне пришёл подпрыгивающий от изумления коллега, и показал примерно вот такой бенчмарк. По его замерам выходило, что
Array.IndexOf
по массиву с int в 300 раз быстрее применения того же метода на массиве uint. Более того, uint что-то там ещё и аллоцирует!Ответ на эту загадку, конечно, элементарный, но показательный. Кто-то знает, в чём проблема?
—
Так как прошло достаточно времени, то можно написать правильный ответ прямо тут: дело в том, что при вызове
Array.IndexOf(uint_array, 15)
, число 15 воспринимается компилятором как int, а значит перегрузка IndexOf<T>(T[] array, T value)
не подходит. А значит, выбирается метод IndexOf(object[] array, object value)
. Вывод: проблему сложно заметить, она не про перформанс, а про внимательность. Поэтому надо быть очень внимательным, когда мы пишем код, ведь нам очень легко получить проблему с производительностью на пустом месте. Вот как тут.
Код бенчмарка в комментариях.
Привет всем! Мы тут с коллегами задумали одну штуку сделать, но чтобы она была интересна и лучше понята, хотелось бы понять уровень присутствующей тут аудитории. Скажите, кем вы себя видите сейчас? Проголосуйте, это очень поможет. Спасибо!
Anonymous Poll
19%
Джун
38%
Миддл
25%
Сеньёр
5%
Тим лид
8%
Тех лид
0%
Менеджер
4%
Архитектор
Логирование и память #решение #память
Недавно разбирался со скоростью работы логирования, а также с тем, сколько логер потребляет памяти.
Эта подсистема не бесплатная и может влиять на производительность. Увы, от логов отказаться нельзя, поэтому хотелось бы всё-таки понять, как писать их быстро и экономно по памяти.
В качестве подопытного кролика выбрал Serilog, как наиболее часто используемую в разных проектах.
Победил чистый
Однако, самым удивительным для меня оказалось то, что простое использование логгера было даже чуть экономичнее аналогов из советов про High-performance logging (ну все эти
Бенчмарк в комментариях.
P.S.: Для специалистов: писал просто в консоль через
Недавно разбирался со скоростью работы логирования, а также с тем, сколько логер потребляет памяти.
Эта подсистема не бесплатная и может влиять на производительность. Увы, от логов отказаться нельзя, поэтому хотелось бы всё-таки понять, как писать их быстро и экономно по памяти.
В качестве подопытного кролика выбрал Serilog, как наиболее часто используемую в разных проектах.
Победил чистый
Serilog.ILogger
, без прослойки в виде Microsoft.Extensions.Logging.ILogger
. И дело не в скорости (она почти одинаковая), а в аллокации - у чистого Serilog она чуть меньше.Однако, самым удивительным для меня оказалось то, что простое использование логгера было даже чуть экономичнее аналогов из советов про High-performance logging (ну все эти
LoggerMessage.Define
и LoggerMessageAttribute). Почему так получилось - понять с наскока не удалось.Бенчмарк в комментариях.
P.S.: Для специалистов: писал просто в консоль через
ConsoleSink
, без изысков, поэтому цифры вот такие.Массив на стеке #решение #память
С момента появления InlineArrayAttribute (мотивация) я хотел на него посмотреть в деле. Напомню, что это атрибут для структур, который "размножает" поле, в котором лежит "элемент массива". Также, атрибут добавляет индексатор для доступа к значениям, чтобы структура стала похожей на массив. Значения через индексатор попадают в сгенерированные поля, откуда могут быть получены позже.
И это всё на стеке, т.е. тот самый zero-allocation.
Единственная проблема: по поведению это массив, т.е. не List, который может расширяться. Это накладывает определённые ограничения на применение данной фичи. Конечно, выход существует и его придумали давно (см. тут): при достижении предела локальных полей мы создаём честный массив, куда складываем "избыток" значений.
С появлением InlineArray эти два подхода можно объединить. Получается неплохо: и быстрее и экономнее по памяти. Кода много, поэтому он тут.
Представить InlineArray как
С момента появления InlineArrayAttribute (мотивация) я хотел на него посмотреть в деле. Напомню, что это атрибут для структур, который "размножает" поле, в котором лежит "элемент массива". Также, атрибут добавляет индексатор для доступа к значениям, чтобы структура стала похожей на массив. Значения через индексатор попадают в сгенерированные поля, откуда могут быть получены позже.
И это всё на стеке, т.е. тот самый zero-allocation.
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10
{
private int _element0;
}
Единственная проблема: по поведению это массив, т.е. не List, который может расширяться. Это накладывает определённые ограничения на применение данной фичи. Конечно, выход существует и его придумали давно (см. тут): при достижении предела локальных полей мы создаём честный массив, куда складываем "избыток" значений.
С появлением InlineArray эти два подхода можно объединить. Получается неплохо: и быстрее и экономнее по памяти. Кода много, поэтому он тут.
Представить InlineArray как
Span
тоже просто - Span<int> span = myBuffer
. Это позволит применить любые подходы работы со Span для всех структур, отмеченных InlineArrayAttribute
.Недавно опубликовали первое видео с последнего DotNext. Мне приятно, что первым опубликованным был некто Евгений, и про производительность.
Почему доклад может быть интересен?
1. Евгений отмечает, что BCL весьма оптимизирована для общих случаев и может считаться одной из самых хороших частей приложений на .NET в смысле производительности.
2. Поднимает важную тему сторонних библиотек, которые не могут или не хотят думать о производительности. Я это называю болезнью Middle-разработчика. Ты как бы уже можешь написать хорошее решение, но ещё не знаешь, как это сделать оптимально.
3. Евгений подтверждает мой личный опыт. Когда я в отчаянии, и в сотый раз пишу свою игру, я почти не пользуюсь сторонними решениями - ну кроме работы с OpenGL/DirectX. Как раз потому, что сторонние решения были написаны до эры Span'ов и прочего инструментария серьёзных парней для улучшения производительности.
4. Затрагивает особенности работы с ArrayPool в многопоточной среде, а также для маленьких массивов.
5. Упоминает интересную тему с LOH. Считается, что LOH это плохо. На самом деле плохо - это фрагментация кучи. Если у вас есть большой массив, его надо обязательно поместить в LOH и это будет более эффективно, чем заставлять GC спотыкаться о маленькие массивы.
6. Рассказывает про логирование. Детальный разбор будет позднее, если руки дойдут. Слушать интересно. Моя позиция неизменна - инфраструктура не должна аффектить производительность основного кода, либо делать это минимально. Будь то логирование или этот ваш MediatR.
7. Уделяет внимание InlineArrayAttribute.
8. Правильно указывает на то, что record мог бы стать отличным местом для кодогенерации. Когда фича только появилась, я так и думал, что это оттуда. Оказалось, увы, нет.
9. Упоминает меня с рассказом про S3. Надеюсь, видео скоро опубликуют.
#философия
Почему доклад может быть интересен?
1. Евгений отмечает, что BCL весьма оптимизирована для общих случаев и может считаться одной из самых хороших частей приложений на .NET в смысле производительности.
2. Поднимает важную тему сторонних библиотек, которые не могут или не хотят думать о производительности. Я это называю болезнью Middle-разработчика. Ты как бы уже можешь написать хорошее решение, но ещё не знаешь, как это сделать оптимально.
3. Евгений подтверждает мой личный опыт. Когда я в отчаянии, и в сотый раз пишу свою игру, я почти не пользуюсь сторонними решениями - ну кроме работы с OpenGL/DirectX. Как раз потому, что сторонние решения были написаны до эры Span'ов и прочего инструментария серьёзных парней для улучшения производительности.
4. Затрагивает особенности работы с ArrayPool в многопоточной среде, а также для маленьких массивов.
5. Упоминает интересную тему с LOH. Считается, что LOH это плохо. На самом деле плохо - это фрагментация кучи. Если у вас есть большой массив, его надо обязательно поместить в LOH и это будет более эффективно, чем заставлять GC спотыкаться о маленькие массивы.
6. Рассказывает про логирование. Детальный разбор будет позднее, если руки дойдут. Слушать интересно. Моя позиция неизменна - инфраструктура не должна аффектить производительность основного кода, либо делать это минимально. Будь то логирование или этот ваш MediatR.
7. Уделяет внимание InlineArrayAttribute.
8. Правильно указывает на то, что record мог бы стать отличным местом для кодогенерации. Когда фича только появилась, я так и думал, что это оттуда. Оказалось, увы, нет.
9. Упоминает меня с рассказом про S3. Надеюсь, видео скоро опубликуют.
#философия
YouTube
Евгений Пешков — Убийцы производительности
Подробнее о конференции DotNext: https://jrg.su/3WmFRE
— —
Одна из причин низкой производительности .NET-приложений — некачественные сторонние библиотеки или неправильное их использование. Например, обычное логирование может превратить всю программу в однопоточную…
— —
Одна из причин низкой производительности .NET-приложений — некачественные сторонние библиотеки или неправильное их использование. Например, обычное логирование может превратить всю программу в однопоточную…
Сколько сейчас платят #деньги
Коллеги, хочу сделать новую рубрику: актуальные данные по зарплатам.
Это, кажется, сильно влияет на нашу производительность. Я сторонник открытых данных о зарплатах, так как это позволяет нам лучше и честнее разговаривать с работодателями о том, что мы хотим и куда мы движемся. Ну и, также, позволяет продавать наше время и умения более выгодно.
Цифры без учёта бонусов и прочего, чистые деньги после налогов по зарплатам в месяц. И только по dotnet'у. Деньги в тысячах.
Архитектор: 400-500.
Тимлид: 350-400.
Техлид: 400-480.
Сеньёр: 380-420.
Миддл: 250-300.
Джун: среди моих знакомых нет, извините.
Видно, что зарплаты хорошие, достойные. В разных компаниях к этому предлагают ещё бонусы, тринадцатые зарплаты и разовые премии.
Важные замечания:
1. Данные основаны на опросах моих знакомых и знакомых знакомых. Выборка не репрезентативная, то есть это моё личное мнение, а также мнение тех, кто мне дал цифры. Для среднего по больнице и для вычислений не подходит.
2. Ценники даются как есть, то есть без выводов. Каждый их делает для себя.
3. Для меня нет разницы между компанией Рога и Копыта и большой компанией. Как получилось, так и получилось.
4. Данные по разным странам, эквивалент в рублях.
5. Игропром не учитывается. Просто тупо нет знакомых.
6. Ещё раз: премии не учитываются. Да, понятно, что премии могут быть x100 от ЗП. Но у меня просто нет таких данных.
7. Если у вас есть какие то другие данные или замечания - пишите. Давайте делиться информацией о рынке труда.
Коллеги, хочу сделать новую рубрику: актуальные данные по зарплатам.
Это, кажется, сильно влияет на нашу производительность. Я сторонник открытых данных о зарплатах, так как это позволяет нам лучше и честнее разговаривать с работодателями о том, что мы хотим и куда мы движемся. Ну и, также, позволяет продавать наше время и умения более выгодно.
Цифры без учёта бонусов и прочего, чистые деньги после налогов по зарплатам в месяц. И только по dotnet'у. Деньги в тысячах.
Архитектор: 400-500.
Тимлид: 350-400.
Техлид: 400-480.
Сеньёр: 380-420.
Миддл: 250-300.
Джун: среди моих знакомых нет, извините.
Видно, что зарплаты хорошие, достойные. В разных компаниях к этому предлагают ещё бонусы, тринадцатые зарплаты и разовые премии.
Важные замечания:
1. Данные основаны на опросах моих знакомых и знакомых знакомых. Выборка не репрезентативная, то есть это моё личное мнение, а также мнение тех, кто мне дал цифры. Для среднего по больнице и для вычислений не подходит.
2. Ценники даются как есть, то есть без выводов. Каждый их делает для себя.
3. Для меня нет разницы между компанией Рога и Копыта и большой компанией. Как получилось, так и получилось.
4. Данные по разным странам, эквивалент в рублях.
5. Игропром не учитывается. Просто тупо нет знакомых.
6. Ещё раз: премии не учитываются. Да, понятно, что премии могут быть x100 от ЗП. Но у меня просто нет таких данных.
7. Если у вас есть какие то другие данные или замечания - пишите. Давайте делиться информацией о рынке труда.
CollectionsMarshal #память
Ваши коллеги любят
Ваши коллеги любят
Вам надоело передавать структуры через Nullable<T> и вы мечтаете быть модным и шелковистым? Опять выход рядом! Обратите внимание на конструкцию
Короче говоря, если кто не знал, есть такой интересный класс CollectionsMarshal. Иногда помогает делать странное. Например, достучаться до внутренних массивов BCL-коллекций и работать с ними, когда вам нужны ссылки на элементы.
Ваши коллеги любят
List<T>
, а вы желаете окунуться в мир Span<T>
? Выход есть!
List<int> list = [1, 2, 3, 4, 5];
var span = CollectionsMarshal.AsSpan(list);
Ваши коллеги любят
Dictionary<TKey, TValue>
, а вы, в тайне от них, мечтаете пощупать механику работы с ref
? Выход есть снова!
var dic = new Dictionary<int, int>
{
{ 1, 1 },
{ 2, 2 }
};
ref var value = ref CollectionsMarshal.GetValueRefOrNullRef(dic, 2);
if (!Unsafe.IsNullRef(ref value)) value = 2222;
Console.WriteLine(dic[2]);
Вам надоело передавать структуры через Nullable<T> и вы мечтаете быть модным и шелковистым? Опять выход рядом! Обратите внимание на конструкцию
Unsafe.IsNullRef(ref value)
из предыдущего кода. Если в словарик передать не существующий ключ, то он сделает return ref Unsafe.NullRef<T>()
. Этот результат очень удобно проверять на null.Короче говоря, если кто не знал, есть такой интересный класс CollectionsMarshal. Иногда помогает делать странное. Например, достучаться до внутренних массивов BCL-коллекций и работать с ними, когда вам нужны ссылки на элементы.
Docs
CollectionsMarshal Класс (System.Runtime.InteropServices)
Небезопасный класс, предоставляющий набор методов для доступа к базовым представлениям коллекций данных.
Антиреклама .NET #решение #память #скорость
Я специально не писал про Garnet, хотя и признаю - это отличная тема про производительность.
Однако, с моей точки зрения, это весьма сомнительная штуковина в плане имплементации (см. вот эту дискуссию). Да, скорость это круто. Да, оно работает. Да, я бы на него перешёл хотя бы из-за лицензии. Но я не понимаю коллег, которые его создали. Ну, чисто как разработчик. Ведь что мы имеем?
Множество
Зачем это было? Мол, прикинь, братва, как много хаков можно написать в коде? Супер, спасибо. А мы не знали.
Я, после этого, не понимаю что отвечать своим знакомым джавистам. Они спрашивают, мол, шарписты реально пишут вот так? А я им пытаюсь объяснить про студентов, про много денег и времени, которые у них были. И про то, что я занимаюсь не этим. Мол, если мне надо прям вот так, я иду на Rust, а не мучаю кошек.
Хотелось бы видеть от MS настоящие проекты на обычном C#. Особенно, если они его рекламируют как то, что должно привлечь новых разработчиков. Увы, кажется, получилась антиреклама.
Я специально не писал про Garnet, хотя и признаю - это отличная тема про производительность.
Однако, с моей точки зрения, это весьма сомнительная штуковина в плане имплементации (см. вот эту дискуссию). Да, скорость это круто. Да, оно работает. Да, я бы на него перешёл хотя бы из-за лицензии. Но я не понимаю коллег, которые его создали. Ну, чисто как разработчик. Ведь что мы имеем?
Множество
unsafe
, свой собственный менеджер памяти для того, чтобы миновать GC. Поверх этого имеем кучу специальных подходов, которые могли быть заменены на подходы современного C#. Кстати, без изоленты и палок. Зачем это было? Мол, прикинь, братва, как много хаков можно написать в коде? Супер, спасибо. А мы не знали.
Я, после этого, не понимаю что отвечать своим знакомым джавистам. Они спрашивают, мол, шарписты реально пишут вот так? А я им пытаюсь объяснить про студентов, про много денег и времени, которые у них были. И про то, что я занимаюсь не этим. Мол, если мне надо прям вот так, я иду на Rust, а не мучаю кошек.
Хотелось бы видеть от MS настоящие проекты на обычном C#. Особенно, если они его рекламируют как то, что должно привлечь новых разработчиков. Увы, кажется, получилась антиреклама.
Мониторинг приложения #решение #память
В Rider появился инструмент поверхностного мониторинга работающего приложения. Этот функционал IDE будет знаком пользователям Visual Studio.
1. Мониторинг запускается при старте приложения. Начинают бежать графики размеров куч и загрузки CPU. Полезно для того, чтобы быстро кинуть взгляд на состояние приложения.
2. Вкладка Counters полезна счётчиками запросов к вашему ASP.NET приложению. Коллегам от перформансного цеха там будет интересна фрагментация кучи, GC-time в миллисекундах и чёткие циферки по размерам куч.
3. Вкладка Environment содержит ключи и значения, с которыми запущено приложение.
4. Особенно приятно, что всё это великолепие можно отключить.
В Rider появился инструмент поверхностного мониторинга работающего приложения. Этот функционал IDE будет знаком пользователям Visual Studio.
1. Мониторинг запускается при старте приложения. Начинают бежать графики размеров куч и загрузки CPU. Полезно для того, чтобы быстро кинуть взгляд на состояние приложения.
2. Вкладка Counters полезна счётчиками запросов к вашему ASP.NET приложению. Коллегам от перформансного цеха там будет интересна фрагментация кучи, GC-time в миллисекундах и чёткие циферки по размерам куч.
3. Вкладка Environment содержит ключи и значения, с которыми запущено приложение.
4. Особенно приятно, что всё это великолепие можно отключить.
Свой Enumerator #память
Могие почему-то боятся делать перечислители (Enumerator) для своих коллекций, выставляя наружу внутренние массивы
Давайте не будем так делать. Перечислители писать легко, благодаря duck-typing'у, который только и требует от нас написать одно свойство (Current) и два метода (MoveNext и GetEnumerator). Для примера, вот так будет выглдяеть enumerator для
В нашей собственной коллекции мы можем также, как и выше, воспользоваться duck-typing'ом, банально реализовав метод
Таким образом мы решаем сразу несколько проблем:
1. Мы не выставляем наружу внутреннюю коллекцию.
2. Мы возвращаем нормальный enumerator в виде
3. Мы не производим замыкание переменных (clousure) при перечислении (см. свойство
Код примера в комментариях, если я объяснил слишком туманно.
Могие почему-то боятся делать перечислители (Enumerator) для своих коллекций, выставляя наружу внутренние массивы
List
или Dictionary
. Чуть более смелые разработчики, желая сохранить инкапсуляцию, выставляют из сущностей IEnumerable
или даже более правильный IReadOnlyCollection
, делая свои коллекции приватными, но доступными через свойство. А вот если нам нужно что-то сделать перед передачей элемента коллекции из сущности, то добро пожаловать в LINQ: коллеги просто возвращают IEnumerable
где начинают городить в возвращаемом свойстве что-то вроде _collection.Select(id => new Actor(conext, id))
. Давайте не будем так делать. Перечислители писать легко, благодаря duck-typing'у, который только и требует от нас написать одно свойство (Current) и два метода (MoveNext и GetEnumerator). Для примера, вот так будет выглдяеть enumerator для
ref struct
(обратите внимание, что я использую современный синтакс C#).
public ref struct Enumerator(int[] collection, MyContext context) {
public readonly Result Current => new(context, collection[_index]);
private int _index = -1;
public bool MoveNext() => ++_index < collection.Length;
}
В нашей собственной коллекции мы можем также, как и выше, воспользоваться duck-typing'ом, банально реализовав метод
GetEnumerator
:
pubic class MyCollection {
private int[] _collection;
private MyContext _context;
...
public Enumerator GetEnumerator() => new(_collection, _context);
...
}
Таким образом мы решаем сразу несколько проблем:
1. Мы не выставляем наружу внутреннюю коллекцию.
2. Мы возвращаем нормальный enumerator в виде
struct
, который ничего не аллоцирует.3. Мы не производим замыкание переменных (clousure) при перечислении (см. свойство
Current
, где можно нагородить любую логику).Код примера в комментариях, если я объяснил слишком туманно.
Собираем строку на стеке #память
Напоминаю про чудесный ValueStringBuilder, который находится в недрах .NET и является
Чтобы его использовать в своём приложении необходимо... просто скопировать его код из репозитория .NET.
Штука до боли простая - это
Далее мы можем работать с ValueStringBuilder'ом так, будто это обычный StringBuilder - у них очень похожие API. Во многом благодаря новому интерфейсу
Наполнив ValueStringBuilder, не спешим создавать из него строку - в современном .NET много где принимают
Однако, не всё радужно. ValueStringBuilder штука особенная, а значит требует особого использования:
1. Мы создаём Span на stack'e, а он не резиновый. Это значит, что создавая первоначальный буффер размером в 1024 символа мы несколько рискуем.
2. После завершения работы необходимо ВСЕГДА вызывать ToString или Dispose, чтобы точно вернуть внутренний массив обратно в пул. Был он взят или нет - мы не знаем.
P.S.: Когда на собеседованиях я спрашиваю про то, как создать строку с минимальными аллокациями, то я считаю ответ про StringBuilder правильным, но на 4. Ответ на 5 - ValueStringBuilder. Наверное, так делаю не только я.
P.P.S: Если вы хотите сразу перейти к тому, к чему я веду в нескольких постах про ValueStringBuilder - см. вот этот комментарий. Интриги не будет, но вы научитесь использовать современные штуки .NET.
Напоминаю про чудесный ValueStringBuilder, который находится в недрах .NET и является
internal
. Это идеальная вещь для замены StringBuilder
на коротких строках. Фактически, он является одним из распространённых подходов к написанию zero-allocation кода. Чтобы его использовать в своём приложении необходимо... просто скопировать его код из репозитория .NET.
Штука до боли простая - это
ref struct
, которая принимает в конструктор Span<char>
. Обычно его создают на стеке путём stackallock char[256]
. Если в процессе создания строки выяснятся, что переданного Span не хватает - используется честный массив из ArrayPool
.
internal ref partial struct ValueStringBuilder {
private char[]? _arrayToReturnToPool;
private Span<char> _chars;
private int _pos;
public ValueStringBuilder(Span<char> initialBuffer)
{
_arrayToReturnToPool = null;
_chars = initialBuffer;
_pos = 0;
}
...
private void Grow(int additionalCapacityBeyondPos)
{
int newCapacity = _pos + additionalCapacityBeyondPos);
char[] poolArray = ArrayPool<char>.Shared.Rent(newCapacity);
...
}
Далее мы можем работать с ValueStringBuilder'ом так, будто это обычный StringBuilder - у них очень похожие API. Во многом благодаря новому интерфейсу
ISpanFormattable
и вот этому методу. Пример использования:
var vsb = new ValueStringBuilder(stackallock char[256]);
vsb.AppendSpanFormattable(DateTime.Now);
vsb.Append(5)
Наполнив ValueStringBuilder, не спешим создавать из него строку - в современном .NET много где принимают
ReadOnlySpan<char>
, а значит нам не нужно делать ToString
. Для использования собранных char'иков, нужно лишь передать их в необходимое место, используя метод AsSpan
. Однако, не всё радужно. ValueStringBuilder штука особенная, а значит требует особого использования:
1. Мы создаём Span на stack'e, а он не резиновый. Это значит, что создавая первоначальный буффер размером в 1024 символа мы несколько рискуем.
2. После завершения работы необходимо ВСЕГДА вызывать ToString или Dispose, чтобы точно вернуть внутренний массив обратно в пул. Был он взят или нет - мы не знаем.
P.S.: Когда на собеседованиях я спрашиваю про то, как создать строку с минимальными аллокациями, то я считаю ответ про StringBuilder правильным, но на 4. Ответ на 5 - ValueStringBuilder. Наверное, так делаю не только я.
P.P.S: Если вы хотите сразу перейти к тому, к чему я веду в нескольких постах про ValueStringBuilder - см. вот этот комментарий. Интриги не будет, но вы научитесь использовать современные штуки .NET.
Дата и StringBuilder #память #бенч
При работе со
Вроде бы ничего страшного, вроде бы очень очень маленькая аллокация. Но на больших объемах GC будет чаще собирать мусор, чаще нагружать процессор, а значит будет оставаться меньше процессорного времени для бизнесовых задач. Не надо так.
Победить это просто. Мы уже знаем про метод
Результаты улучшения на скриншоте. Обратите внимание, что аллокация пропала и результат очень похож на результаты с ValueStringBuilder.
Код бенчмарка в комментариях.
P.S.: Если вы сразу хотите перейти к правильному варианту, минуя ValueStringBuilder'ы и прочие ухищрения - см. вот этот коммент. Другой вопрос, что без понимания КАК это работает, будет сложно это заиспользовать - выглядит как натуральная магия.
При работе со
StringBuilder
есть ещё одна неприятность: метод StringBuilder.Append
прекрасен, но, по какой-то странной для меня причине, не имеет перегрузки для DateTime. Я знаю, что многие разработчики просто передают туда дату и... попадают на boxing, поскольку будет выбран метод с сигнатурой, принимающей object
. Либо, что тоже странно, коллеги просто делают dateTime.ToString()
, аллоцируя промежуточную строку и просто передавая её в StringBuilder.Append.Вроде бы ничего страшного, вроде бы очень очень маленькая аллокация. Но на больших объемах GC будет чаще собирать мусор, чаще нагружать процессор, а значит будет оставаться меньше процессорного времени для бизнесовых задач. Не надо так.
Победить это просто. Мы уже знаем про метод
AppendSpanFormattable
из реализации ValueStringBuilder
, который принимает значения, реализующие ISpanFormattable
. Следовательно, нам нужно просто создать метод-расширение для StringBuilder'a с почти таким же кодом:
public static StringBuilder AppendSpanFormattable<T>(
this StringBuilder sb,
T value, string format, int bufferSize = 64) where T: ISpanFormattable
{
Span<char> buffer = stackalloc char[bufferSize];
if (value.TryFormat(buffer, out var written, format, null)) {
sb.Append(buffer[..written]);
}
else {
sb.Append(value);
}
return sb;
}
Результаты улучшения на скриншоте. Обратите внимание, что аллокация пропала и результат очень похож на результаты с ValueStringBuilder.
Код бенчмарка в комментариях.
P.S.: Если вы сразу хотите перейти к правильному варианту, минуя ValueStringBuilder'ы и прочие ухищрения - см. вот этот коммент. Другой вопрос, что без понимания КАК это работает, будет сложно это заиспользовать - выглядит как натуральная магия.
Бежим по дереву #алгоритм #память #скорость
Дерево - очень полезная структура данных. Помимо того, что про него спрашивают на собеседованиях, дерево помогает хранить иерархически упорядоченные данные. Например, дерево элементов HTML, дерево зависимостей сущностей в игре, дерево подразделений в компании. Имплементация дерева лаконична и проста:
Используя связь по ссылке (ведь
При работе с деревом, иногда возникает необходимость собрать ВСЕ дочерние элементы. Например, нам надо ответить на вопрос сколько людей работает во всём "Департаменте IT". Это означает, что мы должны просуммировать всех людей, входящих в родительский департамент, потом всех людей из дочерних отделов, а затем всех людей в отделах, входящих в эти отделы, вплоть до самого низа организационной иерархии.
Для решения этой задачи мы можем написать метод с использованием страшного и мощного
Получается красиво и лакнично. Но, увы, очень прожорливо по памяти. Напомню, что
Впрочем, человечество заметило эту проблему и придумало не только алгоритмы обхода дерева, но и их эффективные имплементации.
Побный подход не только ускоряет работу (почти в 4 раза), но и позволяет почти избавиться от аллокации (48 байт против 1.2Мб).
Текст бенчмарка в комментариях. Результаты бенчмарка я тоже помещу в комментарии, так как телеграмм сужает пост, если в нём картинка. С телефона это не заметно, а вот на компьютере - очень, в результате чего код почти невозможно читать.
Дерево - очень полезная структура данных. Помимо того, что про него спрашивают на собеседованиях, дерево помогает хранить иерархически упорядоченные данные. Например, дерево элементов HTML, дерево зависимостей сущностей в игре, дерево подразделений в компании. Имплементация дерева лаконична и проста:
public class Tree<T>(Tree<T>? parent, T value) {
public readonly List<Tree<T>> Children = [];
public readonly Tree<T>? Parent = parent;
public readonly T Value = value;
public Tree<T> Add(T value) {
var child = new Tree<T>(this, value);
Children.Add(child);
return child;
}
}
Используя связь по ссылке (ведь
Tree
это класс), мы можем быстро перемещаться по дереву. Имплементация выше предельно упрощена и лишь передаёт суть происходящего.При работе с деревом, иногда возникает необходимость собрать ВСЕ дочерние элементы. Например, нам надо ответить на вопрос сколько людей работает во всём "Департаменте IT". Это означает, что мы должны просуммировать всех людей, входящих в родительский департамент, потом всех людей из дочерних отделов, а затем всех людей в отделах, входящих в эти отделы, вплоть до самого низа организационной иерархии.
Для решения этой задачи мы можем написать метод с использованием страшного и мощного
yield
: public IEnumerable<Tree<T>> GetChildren() {
if (Children.Count == 0) yield break;
foreach (var child in Children)
{
yield return child;
foreach (var subChild in child.GetChildren(true))
{
yield return subChild;
}
}
}
Получается красиво и лакнично. Но, увы, очень прожорливо по памяти. Напомню, что
yield
не бесплатен и его мощь является следствием генерации объекта перечисления, который является классом, а, значит, каждое его инстанциирование загрязняет память. "Вложенность" при подобном подходе порождает ещё бОльший взрыв потребления памяти. Например, если у родителя 7 дочерних элементов, а у каждого из дочерних ещё 6, а у каждого из них ещё 5... короче, на 13700 элементах потребление памяти составляет 1.2Мб. Это много, особенно, если дерево используется постоянно.Впрочем, человечество заметило эту проблему и придумало не только алгоритмы обхода дерева, но и их эффективные имплементации.
[ThreadStatic]
private static Stack<Tree<T>>? _traverseBuffer;
...
public IEnumerable<Tree<T>> TraverseYield() {
var buffer = _traverseBuffer ?? new Stack<Tree<T>>(128);
_traverseBuffer = null;
foreach (var child in Children)
{
buffer.Push(child);
}
while (buffer.Count > 0)
{
var current = buffer.Pop();
foreach (var child in current.Children)
{
buffer.Push(child);
}
yield return current;
}
_traverseBuffer = buffer;
}
Побный подход не только ускоряет работу (почти в 4 раза), но и позволяет почти избавиться от аллокации (48 байт против 1.2Мб).
Текст бенчмарка в комментариях. Результаты бенчмарка я тоже помещу в комментарии, так как телеграмм сужает пост, если в нём картинка. С телефона это не заметно, а вот на компьютере - очень, в результате чего код почти невозможно читать.
StorageS3 0.6.3 #решение #хранилище
Удивительно, но библиотека соединения с Minio жива и даже используется некоторыми коллегами.
И, что особенно удивительно, коллеги проявляют интерес к тому, чтобы помогать эту библиотеку разрабатывать. Каюсь, первый и единственный PR висел почти год. Уважаемый коллега, который его сделал, прости меня!
Короче говоря, смерджил изменения, добавил пару правок от себя и теперь библиотекой снова можно пользоваться - она в nuget.
Все зависимости обновил, поэтому расход памяти и скорость сравниваются с последними версиями библиотек AWS и Minio. Производительность библиотеки прежняя.
P.S.: Именно с этой библиотекой ездил на конференцию.
Удивительно, но библиотека соединения с Minio жива и даже используется некоторыми коллегами.
И, что особенно удивительно, коллеги проявляют интерес к тому, чтобы помогать эту библиотеку разрабатывать. Каюсь, первый и единственный PR висел почти год. Уважаемый коллега, который его сделал, прости меня!
Короче говоря, смерджил изменения, добавил пару правок от себя и теперь библиотекой снова можно пользоваться - она в nuget.
Все зависимости обновил, поэтому расход памяти и скорость сравниваются с последними версиями библиотек AWS и Minio. Производительность библиотеки прежняя.
P.S.: Именно с этой библиотекой ездил на конференцию.
Коллеги, интересный вопрос поставил один хороший человек.
Оказывается, что если из ASP.NET возвращать массивы байт или строки, то будет задействована подсистема Kestrel, которая накапливает и не отдаёт память. Речь про PinnedBlockMemoryPool, который не отдаёт память, которую он аллоцировал для перекладывания.
Например, в данной ситуации, если последовательно запрашивать данные по 1Гб, то мы очень быстро получим отказ в работе, так как память закончилась.
Возможно кто-то сталкивался с подобным и может поддержать коллегу в его приятном деле по исправлению Kestrel?
P.S.: Я в недоумении, так как в контейнере в docker'e подобной ситуации не наблюдается. Коллега тестировал на Windows, просто запуская проект. В релизе.
Оказывается, что если из ASP.NET возвращать массивы байт или строки, то будет задействована подсистема Kestrel, которая накапливает и не отдаёт память. Речь про PinnedBlockMemoryPool, который не отдаёт память, которую он аллоцировал для перекладывания.
[ApiController, Route("api/[controller]")]
public sealed class TestsController : ControllerBase
{
static readonly byte[] Data = new byte[1_000_000_000];
[HttpGet(nameof(Download))]
public ActionResult<byte[]> Download()
{
GC.Collect();
using process = Process.GetCurrentProcess();
var memory = process.PrivateMemorySize64;
Console.WriteLine($"{memory / 1_000_000} MB");
return Data;
}
}
Например, в данной ситуации, если последовательно запрашивать данные по 1Гб, то мы очень быстро получим отказ в работе, так как память закончилась.
Возможно кто-то сталкивался с подобным и может поддержать коллегу в его приятном деле по исправлению Kestrel?
P.S.: Я в недоумении, так как в контейнере в docker'e подобной ситуации не наблюдается. Коллега тестировал на Windows, просто запуская проект. В релизе.
Songs of Styx #игра
Мне кажется, что "пацаны вообще ребята", когда реализуют свою мечту делать игры. Даже на Java.
Рекомендую игрушечку про строительство средневекового города в фэнтези мире. Осторожно, ранний доступ и пиксельная графика. Перевод есть, но от сообщества.
В наличии сложная экономика, торговля, добыча ресурсов, всякие штучки с улучшением уровня жизни подданных. Дальше я не заходил, но, говорят, там можно что-то захватывать и даже биться.
Видео геймплея тут: https://www.youtube.com/embed/H-DewlsabEM
Сайт игры тут: https://songsofsyx.com
Мне кажется, что "пацаны вообще ребята", когда реализуют свою мечту делать игры. Даже на Java.
Рекомендую игрушечку про строительство средневекового города в фэнтези мире. Осторожно, ранний доступ и пиксельная графика. Перевод есть, но от сообщества.
В наличии сложная экономика, торговля, добыча ресурсов, всякие штучки с улучшением уровня жизни подданных. Дальше я не заходил, но, говорят, там можно что-то захватывать и даже биться.
Видео геймплея тут: https://www.youtube.com/embed/H-DewlsabEM
Сайт игры тут: https://songsofsyx.com
YouTube
Songs of Syx - Trailer
As a king in the world of Syx, you lead your people from small colonies to prosperous mega-cities, empires and beyond. You manage everything from individual subject's needs to global diplomacy, trade and a scheming nobility that might try to usurp you from…