DebuggerDisplay #решение
Когда мы пишем код, мы не должны забывать про тех, кто этот код читает. Это аксиома, поэтому мы стараемся писать такой код, чтобы тот парень, который его читает, не пришёл к нам в полночь с вилами.
При этом, можно пойти немного дальше, и подумать о тех, кто наш код будет отлаживать. Например, взаимодействовать с нашим классом. Например, это будет наш класс User, инстансы которого мы перебираем в foreach или сложили в массив.
В отладчике не очень хочется нажимать на плюсик, и каждый раз пытаться прочитать то, что в этом классе находится. В этом нам помогает DebuggerDisplay.
Эта штука работает, как в VS, так и в Rider. Коллегам не нужно будет углубляться в подробности свойств класса, они сразу увидят в отладчике то самое, что им хотелось бы знать об инстансе конкретного класса.
Я употребляю слово "класс", но на самом деле аттрибут можно применить и для структур. И использовать не только свойства, но и результаты методов, которые возвращают
Мне кажется, что подобный аттрибут должен быть у всех наших библиотечных классов, а также у Dbo и Dto, имеющих человекочитаемые названия. Гораздо проще посмотреть сет данных глазами и найти всё то, что нам нужно.
P.S.: Задали логичный вопрос, почему бы просто не использовать метод
1. Вспоминаем принцип единой ответственности.
2. И получается, что DebuggerDisplay создан для случаев отладки, а не для случаев преобразования объекта в строку. Мы можем пользоваться ToString для отладки, но его назначение - сделать строку.
3. Возможно, нам в отладке не нужна вся строка, которую мы собираемся получить из ToString.
4. Тесты и покрытие. Мы должны покрыть метод
Когда мы пишем код, мы не должны забывать про тех, кто этот код читает. Это аксиома, поэтому мы стараемся писать такой код, чтобы тот парень, который его читает, не пришёл к нам в полночь с вилами.
При этом, можно пойти немного дальше, и подумать о тех, кто наш код будет отлаживать. Например, взаимодействовать с нашим классом. Например, это будет наш класс User, инстансы которого мы перебираем в foreach или сложили в массив.
В отладчике не очень хочется нажимать на плюсик, и каждый раз пытаться прочитать то, что в этом классе находится. В этом нам помогает DebuggerDisplay.
[DebuggerDisplay("{Name} ({Email})")]
public class User {
public string Email;
public string Name;
}
Эта штука работает, как в VS, так и в Rider. Коллегам не нужно будет углубляться в подробности свойств класса, они сразу увидят в отладчике то самое, что им хотелось бы знать об инстансе конкретного класса.
Я употребляю слово "класс", но на самом деле аттрибут можно применить и для структур. И использовать не только свойства, но и результаты методов, которые возвращают
string
.Мне кажется, что подобный аттрибут должен быть у всех наших библиотечных классов, а также у Dbo и Dto, имеющих человекочитаемые названия. Гораздо проще посмотреть сет данных глазами и найти всё то, что нам нужно.
P.S.: Задали логичный вопрос, почему бы просто не использовать метод
ToString
. Мне кажется, что мы путаем тёплое с мягким. 1. Вспоминаем принцип единой ответственности.
2. И получается, что DebuggerDisplay создан для случаев отладки, а не для случаев преобразования объекта в строку. Мы можем пользоваться ToString для отладки, но его назначение - сделать строку.
3. Возможно, нам в отладке не нужна вся строка, которую мы собираемся получить из ToString.
4. Тесты и покрытие. Мы должны покрыть метод
ToString
тестами, либо явно сделать игнор этого метода в покрытии.Байдарки на майские #отдых
Коллеги, я поздравляю вас с прошедшими майскими праздниками! Очень надеюсь, что вам удалось отдохнуть и сделать что-то особенное.
Это важно и для нашей с вами производительности, и для эффективного кода, и для инженерной мысли, и для фантазии, и для всего.
Так случилось, что каждый год я хожу на байдарках по разным местам. Северные реки, тушёнка в бороде, лес и природа. В этот раз были не байдарки, а каяк. И леса были не северные, мангровые, с жарой в +35. Это было супер.
Короче говоря, наш профсоюз шлёт вам категорический шалом!
Удачного возвращения к работе.
Коллеги, я поздравляю вас с прошедшими майскими праздниками! Очень надеюсь, что вам удалось отдохнуть и сделать что-то особенное.
Это важно и для нашей с вами производительности, и для эффективного кода, и для инженерной мысли, и для фантазии, и для всего.
Так случилось, что каждый год я хожу на байдарках по разным местам. Северные реки, тушёнка в бороде, лес и природа. В этот раз были не байдарки, а каяк. И леса были не северные, мангровые, с жарой в +35. Это было супер.
Короче говоря, наш профсоюз шлёт вам категорический шалом!
Удачного возвращения к работе.
DebuggerTypeProxy #решение
Помимо удобного атрибута DebuggerDisplay для отображения в окне отладки текстового представления сущности, существует ещё один мощный атрибут - DebuggerTypeProxy.
Он предоставляет возможность создать совершенно другое представление сущности при отладке, а также добавить или исключить поля и свойства из отображения.
Как можно заметить по картинке, отображение класса изменилось, предлагая возможность сразу взглянуть на claim'ы пользователя. Также, обратите внимание на атрибут
Подобный подход можно применить ещё круче. Например, когда сущность вообще не содержит данные для отображения, но мы можем эти данные каким-то достать.
Например, сущность Entity в популярной ECS вообще не содержит данных о её компонентах. Тем не менее, коллега их достаёт и предоставляет в отладке. Для случая ECS это очень удобно, поскольку, с одной стороны, Entity должны быть максимально легковестными, а с другой, крайне хотелось бы видеть содержимое сущности.
Помимо удобного атрибута DebuggerDisplay для отображения в окне отладки текстового представления сущности, существует ещё один мощный атрибут - DebuggerTypeProxy.
Он предоставляет возможность создать совершенно другое представление сущности при отладке, а также добавить или исключить поля и свойства из отображения.
[DebuggerTypeProxy(typeof(UserProxy))]
[DebuggerDisplay("{Name} ({Email})")]
public sealed class User
{
public UserClaim[] Claims;
...
}
[DebuggerDisplay("{Name}")]
public sealed class UserClaim(string name)
{
public string Name = name;
}
internal sealed class UserProxy (User user)
{
[DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
public UserClaim[] Claims => user.Claims;
}
Как можно заметить по картинке, отображение класса изменилось, предлагая возможность сразу взглянуть на claim'ы пользователя. Также, обратите внимание на атрибут
DebuggerBrowsable
, который позволяет заставить отладчик сразу отображать нужное содержимое, будто бы это коллекция.Подобный подход можно применить ещё круче. Например, когда сущность вообще не содержит данные для отображения, но мы можем эти данные каким-то достать.
Например, сущность Entity в популярной ECS вообще не содержит данных о её компонентах. Тем не менее, коллега их достаёт и предоставляет в отладке. Для случая ECS это очень удобно, поскольку, с одной стороны, Entity должны быть максимально легковестными, а с другой, крайне хотелось бы видеть содержимое сущности.
Закон Парето #философия
Я хочу напомнить, не только тем, кто занимается производительностью, но и вообще всем.
Существует простое правило: 20% усилий дают 80% результата. Это закон Парето, оно же 80/20 или 20/80.
Какие же выводы сделали умные люди до нас из этого, казалось бы, элементарного правила?
1. Если у нас что-то работает не эффективно, то, скорее всего, это хранилище данных, сеть или диск. Может быть алгоритм или неправильная коллекция.
2. Если у нас нет кэша, то надо его прикрутить.
3. Если в процессе оптимизации, потребление ОЗУ или ЦПУ сократилось в 80 раз, не надо пытаться сделать ещё лучше. Количество энергии, затраченной на кофе, почти никогда не окупится.
4. Мы эффективны только 20% времени в день. Усилия сверх этого дадут только 20% результата. То есть переработки это не только дорого, но и не эффективно.
5. Менеджмент почти никогда не оценит 80% усилий команды на рефакторинг.
6. Далее я произнесу слова "энергоэффективные ядра" и "ARM", хотя ни черта в этом не смыслю. Но, кажется, идея того, что процессоры 80% времени выполняют примитивные операции близка к правде.
Короче говоря, не забывайте про 20/80. Это полезная штука, которую надо всегда держать в голове. Особенно в случаях, если мы занимаемся оптимизациями.
P.S.: Коллега правильно заметил, что это всё называется "нормальное распределение Гаусса".
Я хочу напомнить, не только тем, кто занимается производительностью, но и вообще всем.
Существует простое правило: 20% усилий дают 80% результата. Это закон Парето, оно же 80/20 или 20/80.
Какие же выводы сделали умные люди до нас из этого, казалось бы, элементарного правила?
1. Если у нас что-то работает не эффективно, то, скорее всего, это хранилище данных, сеть или диск. Может быть алгоритм или неправильная коллекция.
2. Если у нас нет кэша, то надо его прикрутить.
3. Если в процессе оптимизации, потребление ОЗУ или ЦПУ сократилось в 80 раз, не надо пытаться сделать ещё лучше. Количество энергии, затраченной на кофе, почти никогда не окупится.
4. Мы эффективны только 20% времени в день. Усилия сверх этого дадут только 20% результата. То есть переработки это не только дорого, но и не эффективно.
5. Менеджмент почти никогда не оценит 80% усилий команды на рефакторинг.
6. Далее я произнесу слова "энергоэффективные ядра" и "ARM", хотя ни черта в этом не смыслю. Но, кажется, идея того, что процессоры 80% времени выполняют примитивные операции близка к правде.
Короче говоря, не забывайте про 20/80. Это полезная штука, которую надо всегда держать в голове. Особенно в случаях, если мы занимаемся оптимизациями.
P.S.: Коллега правильно заметил, что это всё называется "нормальное распределение Гаусса".
Forwarded from DotNext — конференция для .NET‑разработчиков
Как снизить потребление памяти в .NET-приложении? Кирилл Бажайкин и в канале @csharp_gepard пишет об эффективном использовании .NET, и на DotNext говорил об этом, а сегодня открываем запись его доклада: https://youtu.be/f94Lmm5eTUw
YouTube
Кирилл Бажайкин — Приемы экономии памяти в .NET
Подробнее о конференции DotNext: https://jrg.su/3WmFRE
— —
Скачать презентацию с сайта DotNext — https://jrg.su/lohpRy
Современные приложения иногда потребляют очень много памяти. И иногда они делают это, казалось бы, на пустом месте. Вот и в практике спикера…
— —
Скачать презентацию с сайта DotNext — https://jrg.su/lohpRy
Современные приложения иногда потребляют очень много памяти. И иногда они делают это, казалось бы, на пустом месте. Вот и в практике спикера…
HashSet vs Array #память #скорость #бенч
Иногда бывает ситуация, когда нам нужно убедиться, что элемент существует в коллекции.
В моей ситуации это хитрая проверка прав пользователя, по которым ведётся простое сопоставление. Я создаю сет или массив, и начинаю поиск по нему.
В результате, мы имеем вот этот бенчмарк. Какие же выводы мы можем сделать из него:
1. Чем меньше коллекция, тем более эффективен подход с простым перебором массива. И тем дороже HashSet.
2. Задачи HashSet'a становятся эффективными по скорости от 100 элементов при непосредственном создании перед поиском.
3. HashSet ест память, исходя из имплементации.
4. Простой массив более эффективен по памяти.
Код в комментариях.
P.S.: Коллега сделал замер чисто поиска, без аллокации. Array там почти как HashSet на 8 элементах.
P.P.S.: Если хочется сравнить с предварительно созданым FrozenSet, то результаты коллега опубликовал тут. Array всё ещё ожидаемо хорош на малых коллекциях.
Иногда бывает ситуация, когда нам нужно убедиться, что элемент существует в коллекции.
В моей ситуации это хитрая проверка прав пользователя, по которым ведётся простое сопоставление. Я создаю сет или массив, и начинаю поиск по нему.
В результате, мы имеем вот этот бенчмарк. Какие же выводы мы можем сделать из него:
1. Чем меньше коллекция, тем более эффективен подход с простым перебором массива. И тем дороже HashSet.
2. Задачи HashSet'a становятся эффективными по скорости от 100 элементов при непосредственном создании перед поиском.
3. HashSet ест память, исходя из имплементации.
4. Простой массив более эффективен по памяти.
Код в комментариях.
P.S.: Коллега сделал замер чисто поиска, без аллокации. Array там почти как HashSet на 8 элементах.
P.P.S.: Если хочется сравнить с предварительно созданым FrozenSet, то результаты коллега опубликовал тут. Array всё ещё ожидаемо хорош на малых коллекциях.
Имплементация алгоритмов #алгоритм
Я иногда заглядываю в этот репозиторий. Мне кажется, тут содержатся неплохие имплементации алгоритмов на C#.
Мы ведь помним, что мало прочитать об алгоритме, найти его реализацию на каком-нибудь C++/Rust и переписать на C#? У нас ведь есть нюансы. Вот тут с этим более менее норм.
А вы где подсматриваете реализацию алгоритмов?
Я иногда заглядываю в этот репозиторий. Мне кажется, тут содержатся неплохие имплементации алгоритмов на C#.
Мы ведь помним, что мало прочитать об алгоритме, найти его реализацию на каком-нибудь C++/Rust и переписать на C#? У нас ведь есть нюансы. Вот тут с этим более менее норм.
А вы где подсматриваете реализацию алгоритмов?
GitHub
GitHub - TheAlgorithms/C-Sharp: All algorithms implemented in C#.
All algorithms implemented in C#. Contribute to TheAlgorithms/C-Sharp development by creating an account on GitHub.
Карты, гексагоны, процедурная генерация #алгоритм #игра
Есть один неочевидный факт обо мне. Даже два.
Во-первых, я участвовал в разработке самой настоящей карточной игры. Она в меру интересный аналог Hearthstone. Нет, это не реклама, это просто желание помочь коллегам, которые всё ещё (!) продолжают работать. Они молодцы.
Во-вторых, я являюсь единственным разработчиком целого графического движка на HTML + Canvas2D. Увы, продемонстрировать не могу, так как одна большая корпорация не только купила его с потрохами, но и вставила в мой прекрасный высокооптимизированный код этот его jQuery. На чём, кстати, наши отношения прекратились.
Так вот, а где же я подчерпнул эти ценные знания, которые позволили мне в этом участвовать? А вот тут, у некого Amit Patel, который рассказывает об A*(A-star), картах для шестигранников (hexagonal grid) и вообще про алгоритмы, связанные с графикой.
Например, предлагаю восхититься вот этим материалом. Ну и попробовать его в деле вот в этой демке. После чего, конечно, рекомендую ознакомиться с кодом вот тут.
Очень-очень рекомендую парнишку. Кстати, перевод одной из его статей (весьма интерактивных) есть и на хабре. При этом, я рекомендую читать именно его сайт, поскольку нет-нет, но иногда он обновляет свои замечательные и захватывающие статьи.
Есть один неочевидный факт обо мне. Даже два.
Во-первых, я участвовал в разработке самой настоящей карточной игры. Она в меру интересный аналог Hearthstone. Нет, это не реклама, это просто желание помочь коллегам, которые всё ещё (!) продолжают работать. Они молодцы.
Во-вторых, я являюсь единственным разработчиком целого графического движка на HTML + Canvas2D. Увы, продемонстрировать не могу, так как одна большая корпорация не только купила его с потрохами, но и вставила в мой прекрасный высокооптимизированный код этот его jQuery. На чём, кстати, наши отношения прекратились.
Так вот, а где же я подчерпнул эти ценные знания, которые позволили мне в этом участвовать? А вот тут, у некого Amit Patel, который рассказывает об A*(A-star), картах для шестигранников (hexagonal grid) и вообще про алгоритмы, связанные с графикой.
Например, предлагаю восхититься вот этим материалом. Ну и попробовать его в деле вот в этой демке. После чего, конечно, рекомендую ознакомиться с кодом вот тут.
Очень-очень рекомендую парнишку. Кстати, перевод одной из его статей (весьма интерактивных) есть и на хабре. При этом, я рекомендую читать именно его сайт, поскольку нет-нет, но иногда он обновляет свои замечательные и захватывающие статьи.
PerformanceBehaviour #скорость #решение
Давайте вспомним, как замерять производительность на уровне "плюс-минус лапоть". Иногда это полезно для верхнеуровневой оценки того, что в нашем приложении выполняется медленно.
Например, если мы пользуемся MediatR, то мы должны знать о концепции Bahaviour - эта такая штука, которая как бы "оборачивает" остальной pipeline выполнения наших handler'ов, pre и post-процессоров.
Чтобы написать behaviour для замеров, мы просто делаем следующее:
Его можно сделать обобщенным и заставить MediatR вставлять его во все запросы.
Минусы:
1.
2. В зависимости от реализации
3. Нужно обращать внимание на порядок регистрации behaviour - это поведение должно быть самым первым.
4. Stopwatch это класс. В принципе, чтобы избежать аллокации мы можем воспользоваться разницей
P.S.: Да, я знаю, что всё это можно сделать с помощью Middleware, если у нас сетевое приложение. Увы, MediatR не всегда используется только в серверных приложениях. Ещё можно посмотреть в сторону Activity и Telemetry.
P.P.S.: Я не рекомендую MediatR, но я учитываю, что его использование распространено.
Давайте вспомним, как замерять производительность на уровне "плюс-минус лапоть". Иногда это полезно для верхнеуровневой оценки того, что в нашем приложении выполняется медленно.
Например, если мы пользуемся MediatR, то мы должны знать о концепции Bahaviour - эта такая штука, которая как бы "оборачивает" остальной pipeline выполнения наших handler'ов, pre и post-процессоров.
Чтобы написать behaviour для замеров, мы просто делаем следующее:
public sealed class PerformanceBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse> {
private static readonly TimeSpan RequestLimit = TimeSpan.FromSeconds(1);
public async Task<TResponse> Handle(
TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken ct) {
var timer = Stopwatch.StartNew();
try
{
return await next();
}
finally
{
timer.Stop();
if (timer.Elapsed > RequestLimit)
{
// пишем, что медленно
}
}
}
}
Его можно сделать обобщенным и заставить MediatR вставлять его во все запросы.
Минусы:
1.
RequestLimit
не может быть меньше, чем Stopwatch.Frequency
. Короче говоря, это подходит для замеров "плюс-минус лапоть" (я бы поставил 1 секунду). Чисто для того, чтобы заметить резкий всплеск в логах и углубиться в исследование.2. В зависимости от реализации
IMediator
(а они, внезапно, бывают разные), наличие или отсутствие behaviour может само по себе накладывать перформансный эффект на выполнение pipeline'a. Однако, это скорее мили- и нано-секунды.3. Нужно обращать внимание на порядок регистрации behaviour - это поведение должно быть самым первым.
4. Stopwatch это класс. В принципе, чтобы избежать аллокации мы можем воспользоваться разницей
Environment.TickCount64
в начале и при окончании операции, чтобы определить прошедшее время. Но, чаще всего, это излишние микрооптимизации в замерах "на глазок". Либо, мы можем воспользоваться ValueStopwatch, как предлагает коллега.P.S.: Да, я знаю, что всё это можно сделать с помощью Middleware, если у нас сетевое приложение. Увы, MediatR не всегда используется только в серверных приложениях. Ещё можно посмотреть в сторону Activity и Telemetry.
P.P.S.: Я не рекомендую MediatR, но я учитываю, что его использование распространено.
ConcurrentDictionary #собес #память
Коллеги, задачка. Прямо с собеса, с пылу-с-жару. Сколько раз значение будет выведено в консоль?
Усложняем:
1. Теперь заменим тип значения у
2. А теперь изменим тип значения на
Как же так? Надо пояснить в чём, собственно, дело. Можно скопировать и воспроизвести у себя. И я настоятельно (!) рекомендую это сделать.
---
С одной стороны, суть происходящего проста: ConcurrentDictionary не гарантирует, что элемент по ключу будет создан один и только один раз. Это делается для исключения проблем, которые могут возникнуть при выполнении неизвестного кода под блокировкой, что верно заметил коллега.
А другой стороны, суть происходящего в сложном сценарии не так проста, но всё ещё, кстати, логична.
Когда мы отпускаем
Причём скорость зависит (c) от всего сразу: размера данных, количества процессоров, разрядности процессора и скорости памяти. Например, размер
Мораль всего этого такова:осторожнее на собеседованиях. Нет, мораль другая - если мы пишем асинхронный код, то было бы неплохо протестировать наши результаты не только у себя, но, также, на целевой машине и у того парня. Возможно, наш гениальный код будет работать тут, но не там, а вот там - он вообще всё сломает.
Коллеги, задачка. Прямо с собеса, с пылу-с-жару. Сколько раз значение будет выведено в консоль?
const string Key = "SomeKey";
var topLevel = new ConcurrentDictionary<string, string>();
for (var i = 1; i < 100; i++) {
var thread = new Thread(Add);
thread.Start(topLevel);
}
static void Add(object? val) {
var dict = (ConcurrentDictionary<string, string>)val!;
dict.GetOrAdd(Key, x => {
lock (Lock.Obj)
{
if (dict.TryGetValue(Key, out var value)) return value;
var res = Guid.NewGuid().ToString();
Console.WriteLine(res);
return res;
}
});
}
Усложняем:
1. Теперь заменим тип значения у
ConcurrentDictionary
на Guid
. Естественно строку var res = Guid.NewGuid().ToString();
нужно исправить как var res = Guid.NewGuid()
.2. А теперь изменим тип значения на
int
.Как же так? Надо пояснить в чём, собственно, дело. Можно скопировать и воспроизвести у себя. И я настоятельно (!) рекомендую это сделать.
---
С одной стороны, суть происходящего проста: ConcurrentDictionary не гарантирует, что элемент по ключу будет создан один и только один раз. Это делается для исключения проблем, которые могут возникнуть при выполнении неизвестного кода под блокировкой, что верно заметил коллега.
А другой стороны, суть происходящего в сложном сценарии не так проста, но всё ещё, кстати, логична.
Когда мы отпускаем
lock
, то железке нужно время записать данные в память. И чем бОльший объем данных мы записываем в память, тем это дольше, что подтверждает эмпирический опыт коллеги и дальнейшее развитие рассуждений.Причём скорость зависит (c) от всего сразу: размера данных, количества процессоров, разрядности процессора и скорости памяти. Например, размер
int
это 4, а string
- 8. И, если повезёт, это, в свою очередь, равно sizeof(IntPtr)
или nint
(в зависимости от разрядности процессора). Если разрядность позволяет записать данные в память за одну операцию, то это дает другим потокам (ядрам) больше времени посоревноваться на скорость. Если поток всего один - то конкуренции нет.Мораль всего этого такова:
Random #скорость
Когда мы используем
Более того, чаще всего Random используется просто для некоторой случайности или её имитации. Например, в тестах, UI или компьютерных играх. Последние особенно круто вводят игроков в заблуждение, поскольку у этих ребят каждый кадр на счету.
Ни для кого не будет открытием, что
Но что, если нам нужно быстрее? Ответ, как всегда, есть.
Если нам нужна некоторая последовательность чисел, которая просто случайная для 5-10 секунд, то нам поможет их запись в отдельный массив. Да, последовательность цифр будет одинаковая, но за счёт того, что они берутся в разное время для разных сущностей или ситуаций, мы получаем... случайность.
Естественно, раз в некоторый такт системы (см. метод
Надо ли говорить, что такой подход сильно быстрее тех алгоритмов, которые существуют в .NET "из коробки". Естественно, с ограничениями, которые можно заметить на сравнении 100 и 1000 в бенчмарке.
Код в комментариях.
P.S.: Я этот подход явно где-то подглядел. Если кто-то знает научное название, то напишите в комментах - всем будет приятнее и понятнее.
P.P.S: Коллега прогнал бенч на Intel + Win11 - результат тут. Он интересный.
Когда мы используем
Random
, мы явно не имеем ввиду ничего криптостойкого. Собственно, в документации прямо так и сказано: генерирует последовательность чисел, отвечающую определенным статистическим критериям случайности. Это так называемые псевдо-случайные числа.Более того, чаще всего Random используется просто для некоторой случайности или её имитации. Например, в тестах, UI или компьютерных играх. Последние особенно круто вводят игроков в заблуждение, поскольку у этих ребят каждый кадр на счету.
Ни для кого не будет открытием, что
Random.Shared
быстрее обычного Random
за счёт имплементации более интересного алгоритма.Но что, если нам нужно быстрее? Ответ, как всегда, есть.
Если нам нужна некоторая последовательность чисел, которая просто случайная для 5-10 секунд, то нам поможет их запись в отдельный массив. Да, последовательность цифр будет одинаковая, но за счёт того, что они берутся в разное время для разных сущностей или ситуаций, мы получаем... случайность.
Естественно, раз в некоторый такт системы (см. метод
Dice.Update
, который обновляет внутреннюю коллекцию псевдо-случайных чисел) мы можем обновить случайную последовательность, что и создаёт иллюзию случайности вообще.Надо ли говорить, что такой подход сильно быстрее тех алгоритмов, которые существуют в .NET "из коробки". Естественно, с ограничениями, которые можно заметить на сравнении 100 и 1000 в бенчмарке.
Код в комментариях.
P.S.: Я этот подход явно где-то подглядел. Если кто-то знает научное название, то напишите в комментах - всем будет приятнее и понятнее.
P.P.S: Коллега прогнал бенч на Intel + Win11 - результат тут. Он интересный.
LeetCode #скорость
Когда вы тренируете свои навыки в области алгоритмов с помощью сторонних ресурсов - это прекрасно! Честь и хвала таким ресурсам! Там есть задачи, на которых можно тренировать навыки, которые всегда понадобятся намв разработке на собеседовании.
Однако, есть один неприятный нюанс. Даже три. Два понятных, а один - такой себе.
Например, задачка TwoSum. Это тот самый "изян", который спрашивают на собеседованиях в разные конторы. Так что, собственно, мне не нравится?
Во-первых, наше решение запускаются на непонятных машинах. Например, на моём скриншоте решение задачи выполнялось аж 156 миллисекунд.
Да, согласен, у меня банальный брутфорс, решение "в лоб", да и вообще, я это всё написал для примера. Но... 156, да ещё и миллисекунд, перебор. То есть то, на чём запускается наш алгоритм будет, скорее всего, не самой мощной и не самой нормальной тачкой. Следовательно, доверять измерениям скорости нельзя.
Во-вторых, я употребил аж 47 Мб. Гайс, ну, коммон. Я даже ничего не аллоцировал в своём решении. Откуда 47 Мб? Хотя, наверное, это всё та же машина (или процесс), которая поверхностно измеряется в плане потребляемой памяти. Скорее всего, это ценник за весь дотнет вообще. Странно, конечно, но понятно, и тоже принимается.
В-третьих, и, видимо, в главных. Я надеюсь, что в этих задачах никто не гонится за первенство в скорости или потребления памяти. Да, судя по графику, там есть люди, которые выполнили задачу за 80 миллисекунд или за 45 мегабайт... Но по факту, все эти цифры невероятно завышены. Я бы сказал, на порядки. И взяты с потолка. Ну то есть вообще.
Причём даже относительные цифры сомнительны, верить им нельзя. Так, мой алгоритм выдавал то 104, то 156 мс при разных запусках. Так и в чём соревнование? Неужели в предложении купить подписку?... Нет, надеюсь, что это не так.
Код бенчмарка, который работает 105 наносекунд и использует 448 байт памяти без всяких оптимизаций - в комментах. Задачка, ещё раз, вот эта.
P.S.: Предположение о том, почему "47 МБ" - высказано коллегой тут.
Когда вы тренируете свои навыки в области алгоритмов с помощью сторонних ресурсов - это прекрасно! Честь и хвала таким ресурсам! Там есть задачи, на которых можно тренировать навыки, которые всегда понадобятся нам
Однако, есть один неприятный нюанс. Даже три. Два понятных, а один - такой себе.
Например, задачка TwoSum. Это тот самый "изян", который спрашивают на собеседованиях в разные конторы. Так что, собственно, мне не нравится?
Во-первых, наше решение запускаются на непонятных машинах. Например, на моём скриншоте решение задачи выполнялось аж 156 миллисекунд.
Да, согласен, у меня банальный брутфорс, решение "в лоб", да и вообще, я это всё написал для примера. Но... 156, да ещё и миллисекунд, перебор. То есть то, на чём запускается наш алгоритм будет, скорее всего, не самой мощной и не самой нормальной тачкой. Следовательно, доверять измерениям скорости нельзя.
Во-вторых, я употребил аж 47 Мб. Гайс, ну, коммон. Я даже ничего не аллоцировал в своём решении. Откуда 47 Мб? Хотя, наверное, это всё та же машина (или процесс), которая поверхностно измеряется в плане потребляемой памяти. Скорее всего, это ценник за весь дотнет вообще. Странно, конечно, но понятно, и тоже принимается.
В-третьих, и, видимо, в главных. Я надеюсь, что в этих задачах никто не гонится за первенство в скорости или потребления памяти. Да, судя по графику, там есть люди, которые выполнили задачу за 80 миллисекунд или за 45 мегабайт... Но по факту, все эти цифры невероятно завышены. Я бы сказал, на порядки. И взяты с потолка. Ну то есть вообще.
Причём даже относительные цифры сомнительны, верить им нельзя. Так, мой алгоритм выдавал то 104, то 156 мс при разных запусках. Так и в чём соревнование? Неужели в предложении купить подписку?... Нет, надеюсь, что это не так.
Код бенчмарка, который работает 105 наносекунд и использует 448 байт памяти без всяких оптимизаций - в комментах. Задачка, ещё раз, вот эта.
P.S.: Предположение о том, почему "47 МБ" - высказано коллегой тут.
Как читать бенчмарки #бенч
Давайте мы немного вспомним основы, и напомним, как читать результаты BenchmarkDotNet.
Итак, когда мы создали первый бенчмарк мы получаем примерно такую табличку с данными. Казалось бы, ну чего тут такого. Рассказываю.
1. В заголовке мы видим версию BenchmarkDotNet. Это важно, так как .NET меняется, а значит меняется и прибор, с помощью которого его измеряют.
2. Далее следует версия ОС. Это важно. Например, запуск на Windows и Linux может отличаться.
3. Далее идёт информация о процессоре. В данном случае она "Unknown processor", так как это контейнер. Однако, мы не можем сомневаться в том, что тип и разрядность процессора влияют на скорость работы.
4. Далее идёт версия .NET. Напомню, что разница производительности некоторых версий .NET поразительна. Иногда есть деградация, иногда - прорыв. На неё нужно обращать внимание.
5.
6.
7.
8.
9.
10.
Подробно о том, что есть что - пишется после каждого бенчмарка.
Давайте мы немного вспомним основы, и напомним, как читать результаты BenchmarkDotNet.
Итак, когда мы создали первый бенчмарк мы получаем примерно такую табличку с данными. Казалось бы, ну чего тут такого. Рассказываю.
1. В заголовке мы видим версию BenchmarkDotNet. Это важно, так как .NET меняется, а значит меняется и прибор, с помощью которого его измеряют.
2. Далее следует версия ОС. Это важно. Например, запуск на Windows и Linux может отличаться.
3. Далее идёт информация о процессоре. В данном случае она "Unknown processor", так как это контейнер. Однако, мы не можем сомневаться в том, что тип и разрядность процессора влияют на скорость работы.
4. Далее идёт версия .NET. Напомню, что разница производительности некоторых версий .NET поразительна. Иногда есть деградация, иногда - прорыв. На неё нужно обращать внимание.
5.
Method
- имя бенчмарка. Каждый бенчмарк запускается изолировано и сопровождается отдельным прогревом (подробности пока оставим). Мы можем быть уверенными в том, что бенчмарки не влияют друг на друга.6.
Mean
- это время выполнения бенчмарка. Замечу, что по-умолчанию, это усреднённое время выполнения 15 бенчмарков. Иногда - большего количества, если в процессе их выполнения были обнаружены статистические выбросы - тогда количество повторений увеличится вплоть до 100.7.
Ratio
- это отклонение скорости работы отдельных бенчмарков относительно основного (Baseline
). Его можно узнать по цифре "1.00". В данном случае это Storage
.8.
Gen0
, Gen1
, Gen2
- среднее количество сборок мусора по поколению на одно исполнение бенчмарка. Особенно важно, что эта статистика указывает, насколько наш GC (см. ОС и процессор) в нашем сценарии должен успевать собирать мусор.9.
Allocated
- общее количество аллоцированых данных в памяти. Помогает оценить верхнюю границу памяти, затраченной на один бенчмарк.10.
Alloc Ratio
- относительное количество затраченной памяти. Помогает оценивать работу алгоритма относительно Baseline
.Подробно о том, что есть что - пишется после каждого бенчмарка.
Разные платформы и процессоры #бенч
В продолжение разговора о разных ОС и процессорах, который был начат в посте про Random, необходимо понимать следующее.
И это нужно знать. Это нужно проверять. И с этим нужно смириться.
Ваш код на разных платформах и на разных процессорах будет исполняться по разному. Такова реальность. Особенно, если дело касается микрооптимизаций.
Вот, например, у нас простейший бенчмарк. Это старый бенчмарк, который я периодически запускаю на разных версиях .NET и на разных компьютерах. Бенчмарк пытается воспроизвести Array bound check elimination (оптимизацию JIT, которая позволяет избегать проверки границ массива). Штука уже не очень актуальная, но я люблю этот бенч, так как я подсмотрел его аж в 2015 году у некого Андрея.
И вот, мы снова запускаем его в 2024 году на одном .NET 8, но на разных машинах.
Что мы видим:
1. Версия BenchmakDotNet и самого .NET одинаковая.
2. Разные ОС (Windows 11 и MacOS 14.5).
3. Разные процессоры (Ryzen 5800H и M3 Max).
4. Разная скорость исполнения (46 ns против 29 ns).
5. И совершенно разные относительные результаты.
Если на Windows результат
IL код в моей IDE одинаковый.
Вывод: знайте результаты на целевой ОС и целевых процессорах. В идеале, нужно встроить проверку работы ключевых алгоритмов прямо в CI/CD. Всё иное (я говорю про микрооптимизации) - результат на вашей и только вашей машине.
Код бенчмарка в комментариях.
В продолжение разговора о разных ОС и процессорах, который был начат в посте про Random, необходимо понимать следующее.
И это нужно знать. Это нужно проверять. И с этим нужно смириться.
Ваш код на разных платформах и на разных процессорах будет исполняться по разному. Такова реальность. Особенно, если дело касается микрооптимизаций.
Вот, например, у нас простейший бенчмарк. Это старый бенчмарк, который я периодически запускаю на разных версиях .NET и на разных компьютерах. Бенчмарк пытается воспроизвести Array bound check elimination (оптимизацию JIT, которая позволяет избегать проверки границ массива). Штука уже не очень актуальная, но я люблю этот бенч, так как я подсмотрел его аж в 2015 году у некого Андрея.
И вот, мы снова запускаем его в 2024 году на одном .NET 8, но на разных машинах.
Что мы видим:
1. Версия BenchmakDotNet и самого .NET одинаковая.
2. Разные ОС (Windows 11 и MacOS 14.5).
3. Разные процессоры (Ryzen 5800H и M3 Max).
4. Разная скорость исполнения (46 ns против 29 ns).
5. И совершенно разные относительные результаты.
Если на Windows результат
_array.Length
и константы примерно одинаковый, то на MacOS результаты разные, что несколько внезапно и совершенно не понятно.IL код в моей IDE одинаковый.
Вывод: знайте результаты на целевой ОС и целевых процессорах. В идеале, нужно встроить проверку работы ключевых алгоритмов прямо в CI/CD. Всё иное (я говорю про микрооптимизации) - результат на вашей и только вашей машине.
Код бенчмарка в комментариях.
Аутстафф #философия
Коллеги, у меня нет предубеждения перед аутстаффом. Ну, я про работу на галеры. Про компании, которые продают разработчиков другим компаниям. Я лишь хочу указать на некоторые нюансы для коллег с той и с другой стороны.
Итак, в чём преимущества быть аутстаффом:
1. Можно обучиться работать с разными технологиями и проектами. Быстро. Много. Это очень ценный опыт. Резюме будет прекрасным!
2. Рост от джуна до сеньора для серьезных мужиков и девчонок может быть стремительным. Если, конечно, слушать и запоминать. Проверено на собственном опыте.
3. Если проект или технологии не нравятся - их можно быстро сменить. Просто говоришь начальнику галеры, и он договаривается с покупателем. Ну или просит остаться за бонус. Всем хорошо.
4. Вроде как неплохо принимают джунов.
В чем плюсы аутстаффа для покупателя:
1. Экстренный ресурс. Например, быстро закрыть потребность в разработчиках на критических стадиях проекта.
2. Легко уволить. Надо просто не продлевать контракт. Никаких проблем с отработками и дополнительными выплатами - по законодательству аутстафф работает на другую компанию.
3. Чаще всего персонал как минимум уровня middle. Это значит, что коллеги умеют и будут копать. Наверное. Возможно. Если не подсунут джуна.
В чём минусы быть аутстаффом:
1. Могут не найти проект. В этом случае надо будет раскладывать пасьянс. Я серьёзно. Просто ходишь на работу и, например, пишешь какие-то внутренние документы, которые никто не читает. Или прям пасьянс.
2. Иногда ЗП зависит от прибыли с проекта. Например, ЗП это МРОТ, а всё остальное - бонус. В этом случае, получить нормальные деньги можно только тогда, когда есть проект. Иначе - пасьянс.
3. Мутные схемы работы. Например, иногда надо работать под акаунтом человека, который давно уволился. Но покупатель об этом не знает. И надо скрывать. И соответствовать тому чуваку. Помню, я уволился, а под моим акаунтом в одной американской компании ещё лет 5 сидели 10 разных людей.
4. Иногда надо работать на два проекта. Но каждый из покупателей об этом не знает. Например, вы работаете на проект и компанию А, а в Б внезапно надо быстро что-то сделать. Без погружения, без всяких сложностей. Дали задачу, дали доступ к репо - вперёд, есть ночь.
5. Иногда в резюме для покупателя пишут не то, что умеешь, а потом надо как-то притворяться тем, кем ты не являешься.
6. Увольнение стремительное и без объяснений.
7. В своё время в одной крупной американской компании, которая имела деятельность на территории РФ, были бейджики двух разных цветов. И снять его было нельзя. Надо ли говорить, что права разных бейджиков были разными?
В чем минусы аутстаффа для покупателя:
1. Иногда middle не нужен. А senior’ов нет, так как все они ушли в продуктовые компании. Поэтому будут подсовывать мидлов под видом сеньёров.
2. Обучение нового сотрудника не быстрое, а его могут просто не продлить на следующий месяц. И всё обучение «в трубу».
3. Врут в резюме. Или можно наткнуться на кандидата, который вообще не имеет опыта в нужных технологиях, но оплата была за каждого, кто был предоставлен, а поэтому гонят всех подряд. Без оценки. C++ и C#, ну какая, к бесу, разница?
4. Каждого кандидата надо собеседовать. Также, как и обычных. Иначе можно попасть в ситуацию, что подослали джуна с питоном, который работает на нескольких проектах.
5. Ад с доступами. Аутстафф не всегда человек по версии ИБ - нужна куча дополнительных согласований. И, кстати, не факт, что успешных.
6. Нужны хорошие процессы производства. Если у покупателя задачи в трекере описаны тезиснно, документации нет, тестов мало - будет большой проблемой объяснить аутстаффу, а что, собственно, нужно сделать.
Как это связано с производительностью? Если вы лид, то напрямую - вашу команду усиливают, а значит будут требовать результаты. Знайте плюсы и минусы. Ну, а если вы аутстафф - знайте то, в какой ситуации вам надо высекать каменный цветок.
P.S.: Привет моим коллегам с галеры! Это было крутое время!
Коллеги, у меня нет предубеждения перед аутстаффом. Ну, я про работу на галеры. Про компании, которые продают разработчиков другим компаниям. Я лишь хочу указать на некоторые нюансы для коллег с той и с другой стороны.
Итак, в чём преимущества быть аутстаффом:
1. Можно обучиться работать с разными технологиями и проектами. Быстро. Много. Это очень ценный опыт. Резюме будет прекрасным!
2. Рост от джуна до сеньора для серьезных мужиков и девчонок может быть стремительным. Если, конечно, слушать и запоминать. Проверено на собственном опыте.
3. Если проект или технологии не нравятся - их можно быстро сменить. Просто говоришь начальнику галеры, и он договаривается с покупателем. Ну или просит остаться за бонус. Всем хорошо.
4. Вроде как неплохо принимают джунов.
В чем плюсы аутстаффа для покупателя:
1. Экстренный ресурс. Например, быстро закрыть потребность в разработчиках на критических стадиях проекта.
2. Легко уволить. Надо просто не продлевать контракт. Никаких проблем с отработками и дополнительными выплатами - по законодательству аутстафф работает на другую компанию.
3. Чаще всего персонал как минимум уровня middle. Это значит, что коллеги умеют и будут копать. Наверное. Возможно. Если не подсунут джуна.
В чём минусы быть аутстаффом:
1. Могут не найти проект. В этом случае надо будет раскладывать пасьянс. Я серьёзно. Просто ходишь на работу и, например, пишешь какие-то внутренние документы, которые никто не читает. Или прям пасьянс.
2. Иногда ЗП зависит от прибыли с проекта. Например, ЗП это МРОТ, а всё остальное - бонус. В этом случае, получить нормальные деньги можно только тогда, когда есть проект. Иначе - пасьянс.
3. Мутные схемы работы. Например, иногда надо работать под акаунтом человека, который давно уволился. Но покупатель об этом не знает. И надо скрывать. И соответствовать тому чуваку. Помню, я уволился, а под моим акаунтом в одной американской компании ещё лет 5 сидели 10 разных людей.
4. Иногда надо работать на два проекта. Но каждый из покупателей об этом не знает. Например, вы работаете на проект и компанию А, а в Б внезапно надо быстро что-то сделать. Без погружения, без всяких сложностей. Дали задачу, дали доступ к репо - вперёд, есть ночь.
5. Иногда в резюме для покупателя пишут не то, что умеешь, а потом надо как-то притворяться тем, кем ты не являешься.
6. Увольнение стремительное и без объяснений.
7. В своё время в одной крупной американской компании, которая имела деятельность на территории РФ, были бейджики двух разных цветов. И снять его было нельзя. Надо ли говорить, что права разных бейджиков были разными?
В чем минусы аутстаффа для покупателя:
1. Иногда middle не нужен. А senior’ов нет, так как все они ушли в продуктовые компании. Поэтому будут подсовывать мидлов под видом сеньёров.
2. Обучение нового сотрудника не быстрое, а его могут просто не продлить на следующий месяц. И всё обучение «в трубу».
3. Врут в резюме. Или можно наткнуться на кандидата, который вообще не имеет опыта в нужных технологиях, но оплата была за каждого, кто был предоставлен, а поэтому гонят всех подряд. Без оценки. C++ и C#, ну какая, к бесу, разница?
4. Каждого кандидата надо собеседовать. Также, как и обычных. Иначе можно попасть в ситуацию, что подослали джуна с питоном, который работает на нескольких проектах.
5. Ад с доступами. Аутстафф не всегда человек по версии ИБ - нужна куча дополнительных согласований. И, кстати, не факт, что успешных.
6. Нужны хорошие процессы производства. Если у покупателя задачи в трекере описаны тезиснно, документации нет, тестов мало - будет большой проблемой объяснить аутстаффу, а что, собственно, нужно сделать.
Как это связано с производительностью? Если вы лид, то напрямую - вашу команду усиливают, а значит будут требовать результаты. Знайте плюсы и минусы. Ну, а если вы аутстафф - знайте то, в какой ситуации вам надо высекать каменный цветок.
P.S.: Привет моим коллегам с галеры! Это было крутое время!
Микросервисы vs монолит #доклад
Что-то я пропустил доклад некого Станислава о монолитах, через микросервисы и обратно, но уже в модули. Всем, кого данная тема волнует, я рекомендую это видео.
1. Глубоко, как это принято у Станислава, затронута история вопроса и предпосылки, которые толкают нас от монолитов к микросервисам.
2. Отмечен чисто эмпирический эффект, когда разработчики (или требуют заказчики) закладывают ресурсы на каждый микросервис без понимания границы ресурсов кластера при наличии горизонтального размножения сервисов.
3. Рассказана байка о "черной пятнице", когда взрывной рост нагрузки вызывает каскадный эффект на сотне микросервисов, которые пытаются удвоить потребление ресурсов.
4. Также, подробно, как Станислав любит, рассказана история слияния микросервисов обратно в монолит.
5. Продемонстрирована предварительная статистика слияния 11 сервисов в один, что привело к снижению потребления ОЗУ в 5 раз, а процессора - в 2 раза.
6. Затронут вопрос о том, что взаимодействие сервисов тоже стоит процессорного времени и ОЗУ. И это ещё мы забываем про то, что наличие микросервисов ест ресурсы внешних систем - очередей, балансеров, различных демонов и прочее-прочее.
7. Модульная архитектура позволяет отложить вопрос о том, нужен нам модульный монолит или всё-таки микросервисы, так как вы можете принять решение в моменте.
Код Станислав выложил вот сюда.
Также, я настоятельно рекомендую раскрутить некого Руслана ещё раз рассказать, но уже на камеру, вот этот доклад про путь от микросервисов к модулям.
Что-то я пропустил доклад некого Станислава о монолитах, через микросервисы и обратно, но уже в модули. Всем, кого данная тема волнует, я рекомендую это видео.
1. Глубоко, как это принято у Станислава, затронута история вопроса и предпосылки, которые толкают нас от монолитов к микросервисам.
2. Отмечен чисто эмпирический эффект, когда разработчики (или требуют заказчики) закладывают ресурсы на каждый микросервис без понимания границы ресурсов кластера при наличии горизонтального размножения сервисов.
3. Рассказана байка о "черной пятнице", когда взрывной рост нагрузки вызывает каскадный эффект на сотне микросервисов, которые пытаются удвоить потребление ресурсов.
4. Также, подробно, как Станислав любит, рассказана история слияния микросервисов обратно в монолит.
5. Продемонстрирована предварительная статистика слияния 11 сервисов в один, что привело к снижению потребления ОЗУ в 5 раз, а процессора - в 2 раза.
6. Затронут вопрос о том, что взаимодействие сервисов тоже стоит процессорного времени и ОЗУ. И это ещё мы забываем про то, что наличие микросервисов ест ресурсы внешних систем - очередей, балансеров, различных демонов и прочее-прочее.
7. Модульная архитектура позволяет отложить вопрос о том, нужен нам модульный монолит или всё-таки микросервисы, так как вы можете принять решение в моменте.
Код Станислав выложил вот сюда.
Также, я настоятельно рекомендую раскрутить некого Руслана ещё раз рассказать, но уже на камеру, вот этот доклад про путь от микросервисов к модулям.
YouTube
Станислав Сидристый — Гибридная архитектура: слияние микросервисов в монолит по необходимости
Подробнее о конференции DotNext: https://jrg.su/3WmFRE
— —
При необходимости работать в различных окружениях — и на дистанции в несколько сотен серверов, и на одном сервере на вообще все сервисы — возникает целый ряд проблем, совершенно неспецифичных в обычной…
— —
При необходимости работать в различных окружениях — и на дистанции в несколько сотен серверов, и на одном сервере на вообще все сервисы — возникает целый ряд проблем, совершенно неспецифичных в обычной…