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

Для связи: @SBenzenko

Поддержать канал:
- https://boosty.to/netdeveloperdiary
- https://patreon.com/user?u=52551826
- https://pay.cloudtips.ru/p/70df3b3b
Download Telegram
День 1594. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
II. Асинхронное программирование и async/await
Асинхронное программирование — мощный метод повышения производительности в операциях, связанных с вводом-выводом, позволяющий повысить скорость отклика и эффективность приложения.

1. Ограничьте количество одновременных операций
Управление параллелизмом имеет решающее значение для оптимизации производительности. Ограничивая количество одновременных операций в приложении, вы помогаете снизить общую нагрузку на систему.
Плохо:
public async Task ProcessManyItems(List<string> items)
{
var tasks = items.Select(
async item => await ProcessItem(item));
await Task.WhenAll(tasks);
}

Здесь задачи создаются одновременно для каждого элемента без надлежащего ограничения, что может вызвать значительную нагрузку на систему.
Хорошо:
public async Task ProcessManyItems(
List<string> items,
int maxConcurrency = 10)
{
using (var semaphore = new
SemaphoreSlim(maxConcurrency))
{
var tasks = items.Select(async item =>
{
// Ограничиваем конкурентность семафором
await semaphore.WaitAsync();
try
{
await ProcessItem(item);
}
finally
{
semaphore.Release();
}
});

await Task.WhenAll(tasks);
}
}
Без ограничения параллелизма множество задач будут выполняться одновременно, что может привести к большой нагрузке и снижению общей производительности. Вместо этого используйте SemaphoreSlim для управления количеством одновременных операций. Это отличный пример того, как повысить производительность приложения без ущерба для удобства чтения или сопровождения.

2. Используйте UseConfigureAwait(false), где возможно
Плохо:
public async Task<string> LoadDataAsync()
{
var data = await ReadDataAsync();
return ProcessData(data);
}

ConfigureAwait(false) — ценный приём, который может помочь предотвратить взаимоблокировки в асинхронном коде и повысить эффективность, не заставляя продолжения выполняться в исходном контексте синхронизации.
Хорошо:
public async Task<string> LoadDataAsync()
{
var data = await ReadDataAsync()
.ConfigureAwait(false);
return ProcessData(data);
}

Используйте ConfigureAwait(false) всегда в библиотечном коде и приложениях, не связанных с пользовательским интерфейсом.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍14👎6
День 1595. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
III. Параллельные вычисления и библиотека параллельных задач (TPL)
Параллельные вычисления могут помочь использовать мощность многоядерных процессоров и ускорить операции, связанные с ЦП.

1. Используйте параллельные циклы с Parallel.For() и Parallel.ForEach()
Плохо:
private void ProcessData(List<int> data)
{
for (int i = 0; i < data.Count; i++)
{
PerformExpensiveOperation(data[i]);
}
}

Здесь для обработки данных используется стандартный цикл for, что приводит к последовательному выполнению операций. Это не позволяет использовать весь потенциал современных многоядерных процессоров.
Хорошо:
private void ProcessData(List<int> data)
{
Parallel.ForEach(
data, item =>
PerformExpensiveOperation(item));
}

Параллельные циклы могут значительно ускорить обработку больших коллекций за счет распределения нагрузки между несколькими ядрами ЦП. Переключайтесь с обычных циклов for и foreach на их параллельные аналоги всякий раз, когда это возможно и безопасно.

2. Используйте класс Partitioner для эффективного распределения рабочей нагрузки.
Плохо:
private void ProcessData(IEnumerable<int> data)
{
Parallel.ForEach(data, item =>
PerformExpensiveOperation(item));
}

Здесь не уделяется особого внимания оптимизации разделения рабочей нагрузки между параллельными задачами. Это может привести к потенциальным накладным расходам и неравномерному распределению нагрузки.
Хорошо:
private void ProcessData(IEnumerable<int> data)
{
var partitioner = Partitioner.Create(data);
Parallel.ForEach(partitioner, item =>
PerformExpensiveOperation(item));
}

Используя класс Partitioner, вы можете эффективно распределять рабочие нагрузки по частям, уменьшая потенциальные накладные расходы и улучшая балансировку нагрузки между параллельными задачами.

Partitioner создает оптимальные рабочие блоки, чтобы свести к минимуму накладные расходы на синхронизацию задач, что приводит к повышению производительности и распределению рабочей нагрузки для ваших приложений.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍21
День 1596. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
IV. Важность кэширования данных
Кэширование может значительно повысить производительность приложений за счёт сокращения времени, затрачиваемого на выборку и обработку данных.

1. Реализовать кэширование данных с помощью кэша в памяти
Использование кэширования в памяти может значительно сократить время, требующее выборки данных из базы данных, и ускорить приложение.
Плохо:
public Product GetProductById(int id)
{
// Извлечение данных из БД каждый раз
var pr = _dbContext.Products
.FirstOrDefault(p => p.Id == id);
return pr;
}

Здесь данные о продукте извлекаются из базы каждый раз, когда вызывается метод. Это может привести к значительному снижению производительности, особенно если база данных расположена удалённо или находится под большой нагрузкой.
Хорошо:
private static MemoryCache _cache = 
new MemoryCache(new MemoryCacheOptions());

public Product GetProductById(int id)
{
// Извлечение из кэша, если возможно
if (!_cache.TryGetValue(id, out Product pr))
{
pr = _dbContext.Products
.FirstOrDefault(p => p.Id == id);
_cache.Set(id, pr, TimeSpan.FromMinutes(30));
}

return pr;
}

Использование кэширования в памяти для хранения данных сокращает затраты на выборку базы. Используйте MemoryCache для кэширования часто запрашиваемых данных и повышения производительности.

2. Реализуйте кэширование с помощью распределённых систем кэширования
Распределённые системы кэширования, такие как Redis, могут ещё больше повысить производительность вашего приложения за счёт кэширования данных способом, который масштабируется на нескольких серверах и обеспечивает высокую доступность. Например, используем распределённый кэш для извлечения списка популярных продуктов:
private static IDistributedCache _dCache;

public List<Product> GetProducts()
{
var key = "popularProducts";
var cached = _dCache.GetString(key);
if (cached == null)
{
var pr = _dbContext.Products
.Where(p => p.IsPopular).ToList();
_dCache.SetString(key,
JsonConvert.SerializeObject(pr),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(30)
});

return pr;
}
else
{
return JsonConvert
.DeserializeObject<List<Product>>(cached);
}
}

Здесь мы используем распределённое кэширование с помощью Redis для хранения данных о популярных продуктах, что сокращает частоту выборки из базы данных. Используйте распределённые системы кэширования для кэширования на нескольких серверах и улучшения масштабируемости приложений.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍13
День 1597. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
V. Параллелизм и безопасность потоков
Обеспечение потокобезопасности может предотвратить нежелательные ошибки и проблемы с производительностью.

1. По возможности используйте структуры данных без блокировок
Выбор структур данных, таких как ConcurrentBag, ConcurrentQueue или ConcurrentDictionary, может помочь вам обеспечить безопасность потоков в многопоточных сценариях без ущерба для производительности.
Плохо:
private object _sync = new object();
private List<int> _list = new List<int>();

public void Add(int item)
{
lock (_sync)
{
_list.Add(item);
}
}

Здесь используется блокировка для синхронизации доступа к списку, что может привести к конфликтам и снижению производительности.
Хорошо:
private ConcurrentBag<int> _bag =
new ConcurrentBag<int>();
public void Add(int item)
{
_bag.Add(item);
}

Используя структуры данных без блокировок, вы можете свести к минимуму конфликты, повысить производительность и обеспечить потокобезопасность в многопоточных сценариях.

2. Используйте эффективные конструкции синхронизации
Использование SemaphoreSlim, ReaderWriterLockSlim или Monitor, может помочь вам защитить общие ресурсы и обеспечить безопасность потоков, сводя к минимуму конфликты и влияние на производительность.
Плохо: см. выше.

Хорошо:
private SemaphoreSlim _semaphore
= new SemaphoreSlim(1, 1);
private List<int> _list = new List<int>();

public async Task AddAsync(int item)
{
await _semaphore.WaitAsync();
try
{
_list.Add(item);
}
finally
{
_semaphore.Release();
}
}

Эффективные конструкции синхронизации позволяют защитить общие ресурсы и обеспечить безопасность потоков, сводя к минимуму конфликты и влияние на производительность.

3. Используйте Interlocked для атомарных операций
Вы можете выполнять простые атомарные операции, не полагаясь на блокировки, уменьшая конкуренцию и повышая производительность.
Плохо:
private int _counter;
private object _sync = new object();

public void Increment()
{
lock (_syncRoot)
{
_counter++;
}
}

Здесь ключевое слово lock используется для обеспечения потокобезопасности для увеличения счетчика. Однако это может привести к конфликтам и снижению производительности.
Хорошо:
private int _counter;
public void Increment ()
{
Interlocked.Increment(ref _counter);
}

Класс Interlocked позволяет выполнять простые атомарные операции без использования блокировок, что повышает производительность и снижает количество конфликтов. Используйте его, когда это возможно, для таких операций, как инкремент, декремент или добавление.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍12
День 1604. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
VI. Оптимизация обработки исключений
Обработка исключений — важнейший аспект программирования, но неправильное использование может привести к снижению производительности. Посмотрим, как эффективно и ответственно обрабатывать исключения.

1. Избегайте использования исключений для управления потоком
Обработка исключений как части нормального потока исполнения программы может значительно повлиять на производительность, создавая ненужную работу для оптимизатора и приводя к потенциальным проблемам производительности во время выполнения.
Плохо:
try
{
int.Parse(input);
}
catch (FormatException)
{
// обработка неправильного ввода
}
Здесь попытка парсинга недопустимой входной строки вызовет исключение. Генерация исключения здесь не идеальна для производительности и вынуждает обрабатывать FormatException как часть потока исполнения программы.

Хорошо:
if (int.TryParse(input, out int result))
{
// Используем значение
}
else
{
// обработка неправильного ввода
}
Здесь используется метод TryParse, чтобы не полагаться на исключение для потока исполнения. Такой подход обеспечивает лучшую производительность и более чистый код.

2. Используйте фильтры исключений, чтобы свести к минимуму блоки захвата
Фильтры исключений помогают писать эффективный код обработки исключений, который делает блоки захвата более краткими и простыми в обслуживании.
Плохо:
try
{
// …
}
catch (Exception ex)
{
if (ex is InvalidOperationException
|| ex is ArgumentNullException)
{
// обработка этих видов исключений
}
else
{
throw;
}
}
Здесь несколько исключений перехватываются в одном блоке catch с вложенными операторами if, используемыми для определения типа их обработки. Это может привести к более запутанному и сложному в сопровождении коду.

Хорошо:
try
{
// …
}
catch (Exception ex) when (
ex is InvalidOperationException ||
ex is ArgumentNullException)
{
// обработка этих видов исключений
}
Хороший пример демонстрирует использование фильтров исключений. Это позволяет перехватывать исключения только при выполнении определённого условия, что упрощает блоки перехвата и устраняет необходимость в нескольких блоках перехвата или повторной генерации необработанных исключений. См. подробнее о фильтрах исключений

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍8
День 1609. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
VII. Обнуляемость и обнуляемые ссылочные типы
Обработка ссылочных типов, допускающих значение null, является важной частью программирования на C#, особенно для предотвращения исключений NullReferenceException. Рассмотрим несколько советов по безопасной работе с типами, допускающими значение null, без ущерба для производительности.

1. Используйте операторы объединения с null (??, ??=)
Операторы объединения с null помогают писать краткий и производительный код при работе с типами, допускающими значение NULL, гарантируя, что значения null заменяются значением по умолчанию.
Плохо:
string input = GetNullableString();
if (input == null)
{
input = "default";
}
Неудачный пример демонстрирует многословный и менее производительный код при работе с нулевыми значениями.

Хорошо:
var input = GetNullableString() ?? "default";
Здесь используется оператор объединения с нулевым значением, который обеспечивает более лаконичный и эффективный способ обработки нулевых значений в C#. Это обеспечивает лучшую производительность и более понятный код.

2. Используйте обнуляемые ссылочные типы, чтобы избежать исключений NullReferenceException во время выполнения
Обнуляемые ссылочные типы, появившиеся в C# 8.0, помогают перехватывать потенциальные исключения NullReferenceException во время компиляции, а не во время выполнения.
Плохо:
string name = GetName();
int length = name.Length;
Здесь у нас потенциально во время выполнения может возникнуть исключение NullReferenceException, что может привести к неожиданным сбоям.

Хорошо:
string? name = GetName();
int length = name?.Length ?? 0;
Используя обнуляемые ссылочные типы, и условный доступ с нулевым значением через оператор ?., вы можете избежать потенциальных исключений NullReferenceException в своём коде. Это помогает создавать более безопасный и производительный код, который легче понять как во время разработки, так и во время отладки.

Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍13