.NET Разработчик
6.66K subscribers
461 photos
4 videos
14 files
2.19K links
Дневник сертифицированного .NET разработчика. Заметки, советы, новости из мира .NET и C#.

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День четыреста восемьдесят пятый. #PerformanceTips
Лучшие Практики по Производительности в C#. Начало
Недавно нашёл статью со списком «шаблонов кода, которых следует избегать, потому что они плохо работают в смысле производительности». Автор пишет, что все пункты в списке так или иначе вызывали проблемы с производительностью. Хотя, стоит оговориться, что не все советы могут работать во всех случаях.

1. Синхронное ожидание асинхронного кода
Не ожидайте синхронно незавершённых задач. Включая, но не ограничиваясь: Task.Wait, Task.Result, Task.GetAwaiter().GetResult(), Task.WaitAny, Task.WaitAll. Более общий совет: любая синхронная зависимость между двумя потоками пула может вызвать истощение пула потоков.

2. ConfigureAwait
Если ваш код может быть исполнен без захвата контекста синхронизации, используйте ConfigureAwait(false) для каждого вызова await. Однако обратите внимание, что ConfigureAwait имеет смысл только при использовании ключевого слова await. Например, этот код не имеет смысла:
var result = ProcessAsync().ConfigureAwait(false).GetAwaiter().GetResult();
Подробнее о ConfigureAwait в серии постов с тегом #AsyncAwaitFAQ

3. async void
Никогда не используйте async void. Исключение, выброшенное в таком методе, распространяется в контекст синхронизации и обычно приводит к сбою всего приложения. Если вы не можете вернуть задачу в свой метод (например, потому что вы реализуете интерфейс), переместите асинхронный код метод-обёртку и вызовите его:
interface IInterface {
void DoSomething();
}
class Implementation : IInterface {
public void DoSomething() {
_ = DoSomethingAsync();
}
private async Task DoSomethingAsync() {
await Task.Delay(100);
}
}

4. По возможности избегайте слова async
По привычке вы можете написать:
public async Task CallAsync() {
var client = new Client();
return await client.GetAsync();
}
Хотя код семантически корректен, использование ключевого слова async здесь не требуется и может привести к значительным накладным расходам в «горячих путях». Попробуйте удалить его, если это возможно:
public Task CallAsync() {
var client = new Client();
return client.GetAsync();
}
Однако имейте в виду, что вы не можете использовать эту оптимизацию, когда ваш код упакован в блоки (например, try/catch или using):
public async Task Correct() {
using (var client = new Client()) {
return await client.GetAsync();
}
}
public Task Incorrect() {
using (var client = new Client()) {
return client.GetAsync();
}
}
В методе Incorrect, поскольку задача не ожидается внутри блока using, клиент может быть удален до завершения вызова GetAsync.

Продолжение следует…

Источник:
https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
День четыреста восемьдесят шестой. #PerformanceTips
Лучшие Практики по Производительности в C#. Продолжение
5. Сравнения Строк Чувствительные к Культуре
Если у вас нет причин использовать чувствительные к культуре сравнения строк, всегда используйте нечувствительные сравнения (с параметром StringComparison.Ordinal). Хотя это и не имеет большого значения для латиницы из-за внутренних оптимизаций, сравнение происходит на порядок медленнее для других культур (до 2 порядков в Linux). Поскольку сравнение строк является частой операцией в большинстве приложений, такие мелкие задержки быстро накапливаются.

6. ConcurrentBag<T>
Не используйте ConcurrentBag<T> без тестирования производительности. Эта коллекция была разработана для очень специфических случаев использования (когда большую часть времени элемент извлекается из контейнера добавившим его потоком) и страдает от проблем с производительностью, если используется иначе. Если вам нужна конкурентная коллекция, рассмотрите ConcurrentQueue<T>. Подробнее о потокобезопасных коллекциях.

7. ReaderWriterLock<T>/ReaderWriterLockSlim<T>
Не используйте ReaderWriterLock<T>/ReaderWriterLockSlim<T> без тестирования производительности. Стоимость их использования намного выше, чем у простого монитора (используемого с ключевым словом lock). Если количество читателей критического блока не очень велико, уровня параллелизма будет недостаточно для амортизации возросших издержек, и код будет работать хуже.

8. Используйте Лямбда-Выражения Вместо Чистого Предиката
Рассмотрим следующий код:
private static bool Filter(int i) {
return i % 2 == 0;
}
И два варианта вызова:
list.Where(i => Filter(i));
и
list.Where(Filter);
Второй вариант приводит к выделению памяти в куче при каждом вызове, компилируясь в следующую конструкцию:
list.Where(new Func<int,bool>(Filter));
Лямбда-выражение использует оптимизацию компилятора и кэширует делегат в статическое поле.
*Примечание: я быстренько протестировал оба варианта в консольном приложении на предмет быстродействия и использования памяти и не нашёл никаких отличий ни в Framework, ни в Core. Поэтому есть подозрение, что оптимизация происходит в любом случае, а этот совет либо устарел, либо не проверялся автором.

Продолжение следует…

Источник:
https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a