Сколько сейчас платят #деньги
Коллеги, хочу сделать новую рубрику: актуальные данные по зарплатам.
Это, кажется, сильно влияет на нашу производительность. Я сторонник открытых данных о зарплатах, так как это позволяет нам лучше и честнее разговаривать с работодателями о том, что мы хотим и куда мы движемся. Ну и, также, позволяет продавать наше время и умения более выгодно.
Цифры без учёта бонусов и прочего, чистые деньги после налогов по зарплатам в месяц. И только по 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. Если у вас есть какие то другие данные или замечания - пишите. Давайте делиться информацией о рынке труда.
👍34🔥1🤔1
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)
Небезопасный класс, предоставляющий набор методов для доступа к базовым представлениям коллекций данных.
🔥25😁7👀2👍1
Антиреклама .NET #решение #память #скорость
Я специально не писал про Garnet, хотя и признаю - это отличная тема про производительность.
Однако, с моей точки зрения, это весьма сомнительная штуковина в плане имплементации (см. вот эту дискуссию). Да, скорость это круто. Да, оно работает. Да, я бы на него перешёл хотя бы из-за лицензии. Но я не понимаю коллег, которые его создали. Ну, чисто как разработчик. Ведь что мы имеем?
Множество
Зачем это было? Мол, прикинь, братва, как много хаков можно написать в коде? Супер, спасибо. А мы не знали.
Я, после этого, не понимаю что отвечать своим знакомым джавистам. Они спрашивают, мол, шарписты реально пишут вот так? А я им пытаюсь объяснить про студентов, про много денег и времени, которые у них были. И про то, что я занимаюсь не этим. Мол, если мне надо прям вот так, я иду на Rust, а не мучаю кошек.
Хотелось бы видеть от MS настоящие проекты на обычном C#. Особенно, если они его рекламируют как то, что должно привлечь новых разработчиков. Увы, кажется, получилась антиреклама.
Я специально не писал про Garnet, хотя и признаю - это отличная тема про производительность.
Однако, с моей точки зрения, это весьма сомнительная штуковина в плане имплементации (см. вот эту дискуссию). Да, скорость это круто. Да, оно работает. Да, я бы на него перешёл хотя бы из-за лицензии. Но я не понимаю коллег, которые его создали. Ну, чисто как разработчик. Ведь что мы имеем?
Множество
unsafe
, свой собственный менеджер памяти для того, чтобы миновать GC. Поверх этого имеем кучу специальных подходов, которые могли быть заменены на подходы современного C#. Кстати, без изоленты и палок. Зачем это было? Мол, прикинь, братва, как много хаков можно написать в коде? Супер, спасибо. А мы не знали.
Я, после этого, не понимаю что отвечать своим знакомым джавистам. Они спрашивают, мол, шарписты реально пишут вот так? А я им пытаюсь объяснить про студентов, про много денег и времени, которые у них были. И про то, что я занимаюсь не этим. Мол, если мне надо прям вот так, я иду на Rust, а не мучаю кошек.
Хотелось бы видеть от MS настоящие проекты на обычном C#. Особенно, если они его рекламируют как то, что должно привлечь новых разработчиков. Увы, кажется, получилась антиреклама.
👍27👎2
Мониторинг приложения #решение #память
В 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. Особенно приятно, что всё это великолепие можно отключить.
👍21😁16
Свой 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
, где можно нагородить любую логику).Код примера в комментариях, если я объяснил слишком туманно.
👍25❤1
Собираем строку на стеке #память
Напоминаю про чудесный 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.
👍18🔥5
Дата и 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'ы и прочие ухищрения - см. вот этот коммент. Другой вопрос, что без понимания КАК это работает, будет сложно это заиспользовать - выглядит как натуральная магия.
👍15❤10😁1
Бежим по дереву #алгоритм #память #скорость
Дерево - очень полезная структура данных. Помимо того, что про него спрашивают на собеседованиях, дерево помогает хранить иерархически упорядоченные данные. Например, дерево элементов 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Мб).
Текст бенчмарка в комментариях. Результаты бенчмарка я тоже помещу в комментарии, так как телеграмм сужает пост, если в нём картинка. С телефона это не заметно, а вот на компьютере - очень, в результате чего код почти невозможно читать.
🔥33👍12❤5🤯2
Поиск строки в массиве #скорость
В комментариях заметили интересное поведение при поиске строки в массиве. Если сделать
Было предположение, что проблема в
Оказалось верным подтвержденное бенчмарком предложение, что проблема в JIT:
То есть если переписать метод особым образом, то скорость будет норм. Предположение основано на статье, описывающей оптимизации в JIT.
Бенчмарк в комментах. Бенчмарк с предположением там же. Коллеги, также, написали репорт о проблемах с производительностью.
В комментариях заметили интересное поведение при поиске строки в массиве. Если сделать
Array.IndexOf
по массиву строк, то скорость получается несколько ниже простого перебора в foreach
со сравнением через Equals
. Было предположение, что проблема в
EqualityComparer<string>.Default
(который, на самом деле GenericEqualityComparer<string>), но и это оказалось не верным предположением. То есть проблема где-то в Array.IndexOf
. Оказалось верным подтвержденное бенчмарком предложение, что проблема в JIT:
Предполагаю, что в случае выделения цикла в отдельный метод, JIT оптимизатор находит какой-то паттерн применения и выполняет оптимизацию.
То есть если переписать метод особым образом, то скорость будет норм. Предположение основано на статье, описывающей оптимизации в JIT.
Бенчмарк в комментах. Бенчмарк с предположением там же. Коллеги, также, написали репорт о проблемах с производительностью.
👍13🤯2
StorageS3 0.6.3 #решение #хранилище
Удивительно, но библиотека соединения с Minio жива и даже используется некоторыми коллегами.
И, что особенно удивительно, коллеги проявляют интерес к тому, чтобы помогать эту библиотеку разрабатывать. Каюсь, первый и единственный PR висел почти год. Уважаемый коллега, который его сделал, прости меня!
Короче говоря, смерджил изменения, добавил пару правок от себя и теперь библиотекой снова можно пользоваться - она в nuget.
Все зависимости обновил, поэтому расход памяти и скорость сравниваются с последними версиями библиотек AWS и Minio. Производительность библиотеки прежняя.
P.S.: Именно с этой библиотекой ездил на конференцию.
Удивительно, но библиотека соединения с Minio жива и даже используется некоторыми коллегами.
И, что особенно удивительно, коллеги проявляют интерес к тому, чтобы помогать эту библиотеку разрабатывать. Каюсь, первый и единственный PR висел почти год. Уважаемый коллега, который его сделал, прости меня!
Короче говоря, смерджил изменения, добавил пару правок от себя и теперь библиотекой снова можно пользоваться - она в nuget.
Все зависимости обновил, поэтому расход памяти и скорость сравниваются с последними версиями библиотек AWS и Minio. Производительность библиотеки прежняя.
P.S.: Именно с этой библиотекой ездил на конференцию.
👍21❤5
Коллеги, интересный вопрос поставил один хороший человек.
Оказывается, что если из 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, просто запуская проект. В релизе.
🤔18🤯10👍2
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…
❤7👍1
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
тестами, либо явно сделать игнор этого метода в покрытии.👍22❤6🔥3
Байдарки на майские #отдых
Коллеги, я поздравляю вас с прошедшими майскими праздниками! Очень надеюсь, что вам удалось отдохнуть и сделать что-то особенное.
Это важно и для нашей с вами производительности, и для эффективного кода, и для инженерной мысли, и для фантазии, и для всего.
Так случилось, что каждый год я хожу на байдарках по разным местам. Северные реки, тушёнка в бороде, лес и природа. В этот раз были не байдарки, а каяк. И леса были не северные, мангровые, с жарой в +35. Это было супер.
Короче говоря, наш профсоюз шлёт вам категорический шалом!
Удачного возвращения к работе.
Коллеги, я поздравляю вас с прошедшими майскими праздниками! Очень надеюсь, что вам удалось отдохнуть и сделать что-то особенное.
Это важно и для нашей с вами производительности, и для эффективного кода, и для инженерной мысли, и для фантазии, и для всего.
Так случилось, что каждый год я хожу на байдарках по разным местам. Северные реки, тушёнка в бороде, лес и природа. В этот раз были не байдарки, а каяк. И леса были не северные, мангровые, с жарой в +35. Это было супер.
Короче говоря, наш профсоюз шлёт вам категорический шалом!
Удачного возвращения к работе.
🔥30👍5
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 должны быть максимально легковестными, а с другой, крайне хотелось бы видеть содержимое сущности.
🔥19👍5
Закон Парето #философия
Я хочу напомнить, не только тем, кто занимается производительностью, но и вообще всем.
Существует простое правило: 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.: Коллега правильно заметил, что это всё называется "нормальное распределение Гаусса".
🔥20👍10❤3🥱2
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
Современные приложения иногда потребляют очень много памяти. И иногда они делают это, казалось бы, на пустом месте. Вот и в практике спикера…
👍24🔥12❤3
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 всё ещё ожидаемо хорош на малых коллекциях.
🔥27👍6🗿5❤1
Имплементация алгоритмов #алгоритм
Я иногда заглядываю в этот репозиторий. Мне кажется, тут содержатся неплохие имплементации алгоритмов на 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.
🔥17❤6👍5
Карты, гексагоны, процедурная генерация #алгоритм #игра
Есть один неочевидный факт обо мне. Даже два.
Во-первых, я участвовал в разработке самой настоящей карточной игры. Она в меру интересный аналог Hearthstone. Нет, это не реклама, это просто желание помочь коллегам, которые всё ещё (!) продолжают работать. Они молодцы.
Во-вторых, я являюсь единственным разработчиком целого графического движка на HTML + Canvas2D. Увы, продемонстрировать не могу, так как одна большая корпорация не только купила его с потрохами, но и вставила в мой прекрасный высокооптимизированный код этот его jQuery. На чём, кстати, наши отношения прекратились.
Так вот, а где же я подчерпнул эти ценные знания, которые позволили мне в этом участвовать? А вот тут, у некого Amit Patel, который рассказывает об A*(A-star), картах для шестигранников (hexagonal grid) и вообще про алгоритмы, связанные с графикой.
Например, предлагаю восхититься вот этим материалом. Ну и попробовать его в деле вот в этой демке. После чего, конечно, рекомендую ознакомиться с кодом вот тут.
Очень-очень рекомендую парнишку. Кстати, перевод одной из его статей (весьма интерактивных) есть и на хабре. При этом, я рекомендую читать именно его сайт, поскольку нет-нет, но иногда он обновляет свои замечательные и захватывающие статьи.
Есть один неочевидный факт обо мне. Даже два.
Во-первых, я участвовал в разработке самой настоящей карточной игры. Она в меру интересный аналог Hearthstone. Нет, это не реклама, это просто желание помочь коллегам, которые всё ещё (!) продолжают работать. Они молодцы.
Во-вторых, я являюсь единственным разработчиком целого графического движка на HTML + Canvas2D. Увы, продемонстрировать не могу, так как одна большая корпорация не только купила его с потрохами, но и вставила в мой прекрасный высокооптимизированный код этот его jQuery. На чём, кстати, наши отношения прекратились.
Так вот, а где же я подчерпнул эти ценные знания, которые позволили мне в этом участвовать? А вот тут, у некого Amit Patel, который рассказывает об A*(A-star), картах для шестигранников (hexagonal grid) и вообще про алгоритмы, связанные с графикой.
Например, предлагаю восхититься вот этим материалом. Ну и попробовать его в деле вот в этой демке. После чего, конечно, рекомендую ознакомиться с кодом вот тут.
Очень-очень рекомендую парнишку. Кстати, перевод одной из его статей (весьма интерактивных) есть и на хабре. При этом, я рекомендую читать именно его сайт, поскольку нет-нет, но иногда он обновляет свои замечательные и захватывающие статьи.
🔥21👍3❤1