День 1594. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
II. Асинхронное программирование и async/await
Асинхронное программирование — мощный метод повышения производительности в операциях, связанных с вводом-выводом, позволяющий повысить скорость отклика и эффективность приложения.
1. Ограничьте количество одновременных операций
Управление параллелизмом имеет решающее значение для оптимизации производительности. Ограничивая количество одновременных операций в приложении, вы помогаете снизить общую нагрузку на систему.
Плохо:
Хорошо:
2. Используйте UseConfigureAwait(false), где возможно
Плохо:
Хорошо:
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
Советы по Оптимизации Производительности
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(Без ограничения параллелизма множество задач будут выполняться одновременно, что может привести к большой нагрузке и снижению общей производительности. Вместо этого используйте SemaphoreSlim для управления количеством одновременных операций. Это отличный пример того, как повысить производительность приложения без ущерба для удобства чтения или сопровождения.
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);
}
}
2. Используйте UseConfigureAwait(false), где возможно
Плохо:
public async Task<string> LoadDataAsync()ConfigureAwait(false) — ценный приём, который может помочь предотвратить взаимоблокировки в асинхронном коде и повысить эффективность, не заставляя продолжения выполняться в исходном контексте синхронизации.
{
var data = await ReadDataAsync();
return ProcessData(data);
}
Хорошо:
public async Task<string> LoadDataAsync()Используйте ConfigureAwait(false) всегда в библиотечном коде и приложениях, не связанных с пользовательским интерфейсом.
{
var data = await ReadDataAsync()
.ConfigureAwait(false);
return ProcessData(data);
}
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍14👎6
День 1595. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
III. Параллельные вычисления и библиотека параллельных задач (TPL)
Параллельные вычисления могут помочь использовать мощность многоядерных процессоров и ускорить операции, связанные с ЦП.
1. Используйте параллельные циклы с Parallel.For() и Parallel.ForEach()
Плохо:
Хорошо:
2. Используйте класс Partitioner для эффективного распределения рабочей нагрузки.
Плохо:
Хорошо:
Partitioner создает оптимальные рабочие блоки, чтобы свести к минимуму накладные расходы на синхронизацию задач, что приводит к повышению производительности и распределению рабочей нагрузки для ваших приложений.
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
Советы по Оптимизации Производительности
III. Параллельные вычисления и библиотека параллельных задач (TPL)
Параллельные вычисления могут помочь использовать мощность многоядерных процессоров и ускорить операции, связанные с ЦП.
1. Используйте параллельные циклы с Parallel.For() и Parallel.ForEach()
Плохо:
private void ProcessData(List<int> data)Здесь для обработки данных используется стандартный цикл for, что приводит к последовательному выполнению операций. Это не позволяет использовать весь потенциал современных многоядерных процессоров.
{
for (int i = 0; i < data.Count; i++)
{
PerformExpensiveOperation(data[i]);
}
}
Хорошо:
private void ProcessData(List<int> data)Параллельные циклы могут значительно ускорить обработку больших коллекций за счет распределения нагрузки между несколькими ядрами ЦП. Переключайтесь с обычных циклов for и foreach на их параллельные аналоги всякий раз, когда это возможно и безопасно.
{
Parallel.ForEach(
data, item =>
PerformExpensiveOperation(item));
}
2. Используйте класс Partitioner для эффективного распределения рабочей нагрузки.
Плохо:
private void ProcessData(IEnumerable<int> data)Здесь не уделяется особого внимания оптимизации разделения рабочей нагрузки между параллельными задачами. Это может привести к потенциальным накладным расходам и неравномерному распределению нагрузки.
{
Parallel.ForEach(data, item =>
PerformExpensiveOperation(item));
}
Хорошо:
private void ProcessData(IEnumerable<int> data)Используя класс Partitioner, вы можете эффективно распределять рабочие нагрузки по частям, уменьшая потенциальные накладные расходы и улучшая балансировку нагрузки между параллельными задачами.
{
var partitioner = Partitioner.Create(data);
Parallel.ForEach(partitioner, item =>
PerformExpensiveOperation(item));
}
Partitioner создает оптимальные рабочие блоки, чтобы свести к минимуму накладные расходы на синхронизацию задач, что приводит к повышению производительности и распределению рабочей нагрузки для ваших приложений.
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍21
День 1596. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
IV. Важность кэширования данных
Кэширование может значительно повысить производительность приложений за счёт сокращения времени, затрачиваемого на выборку и обработку данных.
1. Реализовать кэширование данных с помощью кэша в памяти
Использование кэширования в памяти может значительно сократить время, требующее выборки данных из базы данных, и ускорить приложение.
Плохо:
Хорошо:
2. Реализуйте кэширование с помощью распределённых систем кэширования
Распределённые системы кэширования, такие как Redis, могут ещё больше повысить производительность вашего приложения за счёт кэширования данных способом, который масштабируется на нескольких серверах и обеспечивает высокую доступность. Например, используем распределённый кэш для извлечения списка популярных продуктов:
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
Советы по Оптимизации Производительности
IV. Важность кэширования данных
Кэширование может значительно повысить производительность приложений за счёт сокращения времени, затрачиваемого на выборку и обработку данных.
1. Реализовать кэширование данных с помощью кэша в памяти
Использование кэширования в памяти может значительно сократить время, требующее выборки данных из базы данных, и ускорить приложение.
Плохо:
public Product GetProductById(int id)Здесь данные о продукте извлекаются из базы каждый раз, когда вызывается метод. Это может привести к значительному снижению производительности, особенно если база данных расположена удалённо или находится под большой нагрузкой.
{
// Извлечение данных из БД каждый раз
var pr = _dbContext.Products
.FirstOrDefault(p => p.Id == id);
return pr;
}
Хорошо:
private static MemoryCache _cache =Использование кэширования в памяти для хранения данных сокращает затраты на выборку базы. Используйте MemoryCache для кэширования часто запрашиваемых данных и повышения производительности.
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;
}
2. Реализуйте кэширование с помощью распределённых систем кэширования
Распределённые системы кэширования, такие как Redis, могут ещё больше повысить производительность вашего приложения за счёт кэширования данных способом, который масштабируется на нескольких серверах и обеспечивает высокую доступность. Например, используем распределённый кэш для извлечения списка популярных продуктов:
private static IDistributedCache _dCache;Здесь мы используем распределённое кэширование с помощью Redis для хранения данных о популярных продуктах, что сокращает частоту выборки из базы данных. Используйте распределённые системы кэширования для кэширования на нескольких серверах и улучшения масштабируемости приложений.
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);
}
}
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍13
День 1597. #TipsAndTricks #PerformanceTips
Советы по Оптимизации Производительности
V. Параллелизм и безопасность потоков
Обеспечение потокобезопасности может предотвратить нежелательные ошибки и проблемы с производительностью.
1. По возможности используйте структуры данных без блокировок
Выбор структур данных, таких как ConcurrentBag, ConcurrentQueue или ConcurrentDictionary, может помочь вам обеспечить безопасность потоков в многопоточных сценариях без ущерба для производительности.
Плохо:
Хорошо:
2. Используйте эффективные конструкции синхронизации
Использование SemaphoreSlim, ReaderWriterLockSlim или Monitor, может помочь вам защитить общие ресурсы и обеспечить безопасность потоков, сводя к минимуму конфликты и влияние на производительность.
Плохо: см. выше.
Хорошо:
3. Используйте Interlocked для атомарных операций
Вы можете выполнять простые атомарные операции, не полагаясь на блокировки, уменьшая конкуренцию и повышая производительность.
Плохо:
Хорошо:
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
Советы по Оптимизации Производительности
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. Избегайте использования исключений для управления потоком
Обработка исключений как части нормального потока исполнения программы может значительно повлиять на производительность, создавая ненужную работу для оптимизатора и приводя к потенциальным проблемам производительности во время выполнения.
Плохо:
Хорошо:
2. Используйте фильтры исключений, чтобы свести к минимуму блоки захвата
Фильтры исключений помогают писать эффективный код обработки исключений, который делает блоки захвата более краткими и простыми в обслуживании.
Плохо:
Хорошо:
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
Советы по Оптимизации Производительности
VI. Оптимизация обработки исключений
Обработка исключений — важнейший аспект программирования, но неправильное использование может привести к снижению производительности. Посмотрим, как эффективно и ответственно обрабатывать исключения.
1. Избегайте использования исключений для управления потоком
Обработка исключений как части нормального потока исполнения программы может значительно повлиять на производительность, создавая ненужную работу для оптимизатора и приводя к потенциальным проблемам производительности во время выполнения.
Плохо:
tryЗдесь попытка парсинга недопустимой входной строки вызовет исключение. Генерация исключения здесь не идеальна для производительности и вынуждает обрабатывать FormatException как часть потока исполнения программы.
{
int.Parse(input);
}
catch (FormatException)
{
// обработка неправильного ввода
}
Хорошо:
if (int.TryParse(input, out int result))Здесь используется метод TryParse, чтобы не полагаться на исключение для потока исполнения. Такой подход обеспечивает лучшую производительность и более чистый код.
{
// Используем значение
}
else
{
// обработка неправильного ввода
}
2. Используйте фильтры исключений, чтобы свести к минимуму блоки захвата
Фильтры исключений помогают писать эффективный код обработки исключений, который делает блоки захвата более краткими и простыми в обслуживании.
Плохо:
tryЗдесь несколько исключений перехватываются в одном блоке catch с вложенными операторами if, используемыми для определения типа их обработки. Это может привести к более запутанному и сложному в сопровождении коду.
{
// …
}
catch (Exception ex)
{
if (ex is InvalidOperationException
|| ex is ArgumentNullException)
{
// обработка этих видов исключений
}
else
{
throw;
}
}
Хорошо:
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 заменяются значением по умолчанию.
Плохо:
Хорошо:
2. Используйте обнуляемые ссылочные типы, чтобы избежать исключений NullReferenceException во время выполнения
Обнуляемые ссылочные типы, появившиеся в C# 8.0, помогают перехватывать потенциальные исключения NullReferenceException во время компиляции, а не во время выполнения.
Плохо:
Хорошо:
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
Советы по Оптимизации Производительности
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();Здесь у нас потенциально во время выполнения может возникнуть исключение NullReferenceException, что может привести к неожиданным сбоям.
int length = name.Length;
Хорошо:
string? name = GetName();Используя обнуляемые ссылочные типы, и условный доступ с нулевым значением через оператор ?., вы можете избежать потенциальных исключений NullReferenceException в своём коде. Это помогает создавать более безопасный и производительный код, который легче понять как во время разработки, так и во время отладки.
int length = name?.Length ?? 0;
Источник: https://dev.to/bytehide/50-c-advanced-optimization-performance-tips-18l2
👍13