День четыреста восемьдесят пятый. #PerformanceTips
Лучшие Практики по Производительности в C#. Начало
Недавно нашёл статью со списком «шаблонов кода, которых следует избегать, потому что они плохо работают в смысле производительности». Автор пишет, что все пункты в списке так или иначе вызывали проблемы с производительностью. Хотя, стоит оговориться, что не все советы могут работать во всех случаях.
1. Синхронное ожидание асинхронного кода
Не ожидайте синхронно незавершённых задач. Включая, но не ограничиваясь:
2. ConfigureAwait
Если ваш код может быть исполнен без захвата контекста синхронизации, используйте
3. async void
Никогда не используйте
По привычке вы можете написать:
Продолжение следует…
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
Лучшие Практики по Производительности в 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 в серии постов с тегом #AsyncAwaitFAQ3. 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. Сравнения Строк Чувствительные к Культуре
Если у вас нет причин использовать чувствительные к культуре сравнения строк, всегда используйте нечувствительные сравнения (с параметром
6. ConcurrentBag<T>
Не используйте
7. ReaderWriterLock<T>/ReaderWriterLockSlim<T>
Не используйте
8. Используйте Лямбда-Выражения Вместо Чистого Предиката
Рассмотрим следующий код:
*Примечание: я быстренько протестировал оба варианта в консольном приложении на предмет быстродействия и использования памяти и не нашёл никаких отличий ни в Framework, ни в Core. Поэтому есть подозрение, что оптимизация происходит в любом случае, а этот совет либо устарел, либо не проверялся автором.
Продолжение следует…
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
Лучшие Практики по Производительности в 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
День четыреста восемьдесят седьмой. #PerformanceTips
Лучшие Практики по Производительности в C#. Продолжение
9. Преобразование Перечислений в Строку
Вызов
10. Сравнения Перечислений
Примечание: это поведение оптимизировано, начиная с .Net Core 2.1
При использовании перечислений в качестве флагов может возникнуть соблазн использовать метод
11. Реализуйте Проверку на Равенство для Структур
При использовании
12. Избегайте Ненужного Боксинга при Использовании Структур с Интерфейсами
Рассмотрим следующий код:
Продолжение следует…
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
Лучшие Практики по Производительности в C#. Продолжение
9. Преобразование Перечислений в Строку
Вызов
Enum.ToString в .Net является дорогостоящим, поскольку для преобразования используется рефлексия, а вызов виртуального метода структуры приводит к боксингу. Этого следует по возможности избегать. Часто перечисления могут быть заменены константами:public enum Numbers {
One, Two, …
}
public static class Numbers {
public const string One = "One";
public const string Two = "Two";
…
}
В обоих случаях можно использовать Numbers.One, Numbers.Two,…10. Сравнения Перечислений
Примечание: это поведение оптимизировано, начиная с .Net Core 2.1
При использовании перечислений в качестве флагов может возникнуть соблазн использовать метод
Enum.HasFlag:[Flags]Этот код приводит к боксингу для преобразования
public enum Options {
Opt1 = 1, Opt2 = 2, Opt3 = 4
}
…
private Options option;
public bool IsOpt2() => option.HasFlag(Options.Opt2);
Options.Opt2 в Enum и для виртуального вызова HasFlag на структуре, делает код необоснованно дорогим. Вместо этого можно использовать бинарные операторы:public bool IsOpt2() => (option & Options.Opt2 == Options.Opt2);Подробнее про битовые флаги
11. Реализуйте Проверку на Равенство для Структур
При использовании
struct в сравнениях (например, при использовании в качестве ключа для словаря) необходимо переопределить методы Equals и GetHashCode. Реализация по умолчанию использует рефлексию и очень медленная. Подробнее12. Избегайте Ненужного Боксинга при Использовании Структур с Интерфейсами
Рассмотрим следующий код:
public class IntValue : IValue {}
public void SendValue(IValue value) {…}
public void LogValue(IValue value) {…}
public void DoStuff() {
var value = new IntValue();
LogValue(value);
SendValue(value);
}
Соблазнительно сделать IntValue структурой, чтобы избежать выделения памяти в куче. Но поскольку AddValue и SendValue принимают интерфейс, а интерфейсы имеют ссылочную семантику, значение будет упаковываться при каждом вызове, сводя на нет преимущества «оптимизации». На самом деле, производительность может быть даже хуже, чем если бы IntValue был классом. Если же вы создаёте API, которому может быть передана структура, попробуйте использовать обобщённые методы:public void SendValue<T>(T value) where T : IValue {…}
public void LogValue<T>(T value) where T : IValue {…}
Хотя на первый взгляд создание таких методов выглядит бесполезным, на самом деле это позволяет избежать боксинга, когда IntValue является структурой.Продолжение следует…
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
День четыреста восемьдесят восьмой. #PerformanceTips
Лучшие Практики по Производительности в C#. Окончание
13. Код Подписчиков CancellationToken Всегда Встраивается
Когда вы отменяете задачу через
14. Код Продолжений TaskCompletionSource Часто Встраивается
Код продолжений
Если у вас нет веских причин этого не делать, всегда используйте этот параметр при создании
Внимание: код также скомпилируется, если вы используете
15. Task.Run / Task.Factory.StartNew
Если у вас нет причин использовать
- Запуск задачи в другом планировщике
- Выполнение задачи в выделенном потоке (с помощью
- Постановка задачи в глобальную очередь пула потоков (с помощью
Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
Лучшие Практики по Производительности в C#. Окончание
13. Код Подписчиков CancellationToken Всегда Встраивается
Когда вы отменяете задачу через
CancellationTokenSource, код всех подписчиков будет выполняться в текущем потоке. Это может привести к незапланированным паузам или даже неочевидным взаимным блокировкам:var cts = new CancellationTokenSource ();Вы не можете отказаться от этого поведения. Поэтому, при отмене через
cts.Token.Register (() => Thread.Sleep (5000));
cts.Cancel (); // Это вызов заблокируется на 5 секунд
CancellationTokenSource, спросите себя, можете ли вы позволить текущему потоку исполнять другой код. Если нет, оберните вызов Cancel в Task.Run, чтобы выполнить его в пуле потоков. Подробнее о CancellationToken.14. Код Продолжений TaskCompletionSource Часто Встраивается
Код продолжений
TaskCompletionSource также часто встраивается. Это хорошая оптимизация, но она может быть причиной неочевидных ошибок. Их можно избежать, передав параметру TaskCompletionSource параметр TaskCreationOptions.RunContinuationsAsynchronously.Если у вас нет веских причин этого не делать, всегда используйте этот параметр при создании
TaskCompletionSource.Внимание: код также скомпилируется, если вы используете
TaskContinuationOptions.RunContinuationsAsynchronously. Но этот параметр будет проигнорирован, и продолжения будут оставаться встроенными. Это удивительно распространенная ошибка, потому что TaskContinuationOptions предшествует TaskCreationOptions при автозаполнении.15. Task.Run / Task.Factory.StartNew
Если у вас нет причин использовать
Task.Factory.StartNew, отдавайте предпочтение Task.Run для запуска фоновой задачи. Task.Run использует более безопасные значения по умолчанию, и, что более важно автоматически разворачивает возвращаемую задачу, что может предотвратить ошибки в асинхронных методах:class Program {
public static async Task ProcessAsync() {
await Task.Delay(2000);
Console.WriteLine("Processing done");
}
static async Task Main(string[] args) {
await Task.Factory.StartNew(ProcessAsync);
Console.WriteLine("End of program");
Console.ReadLine();
}
}
Из кода это не очевидно, но "End of program" будет выведено раньше, чем "Processing done", потому что Task.Factory.StartNew вернёт Task<Task>, а код ожидает завершения только внешней задачи. Исправить это можно, используя либоawait Task.Factory.StartNew(ProcessAsync).Unwrap();либо
await Task.Run(ProcessAsync);
Task.Factory.StartNew лучше использовать в следующих случаях:- Запуск задачи в другом планировщике
- Выполнение задачи в выделенном потоке (с помощью
TaskCreationOptions.LongRunning)- Постановка задачи в глобальную очередь пула потоков (с помощью
TaskCreationOptions.PreferFairness)Источник: https://medium.com/@kevingosse/performance-best-practices-in-c-b85a47bdd93a
День восемьсот сороковой. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно
Разработка программного обеспечения - это поиск компромиссов:
- нормализация против денормализации в реляционных базах данных,
- скорость разработки против качественного кода
и т.п.
Высокопроизводительный код C# обычно требует компромиссов. Разработчики могут пожертвовать удобством сопровождения или безопасностью кода, чтобы код работал быстрее. Но это применимо только к сценариям, в которых уже применяются все шаблоны производительности и лучшие практики, а производительность всё равно требует дальнейшего улучшения.
Существует множество подходов, которые могут помочь разработчикам значительно улучшить производительность приложений, ничем не жертвуя.
Начнём с очевидного:
1. Указывайте ёмкость коллекции
Рассмотрим два почти идентичных метода:
Предварительное указание ёмкости устраняет накладные расходы на выделение, копирование и сборку мусора использованных массивов. Разработчики должны всегда указывать ёмкость коллекции, если они заранее знают, сколько элементов будет в неё добавлено.
Параметр ёмкости работает не только с коллекцией
Продолжение следует…
Источник: https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
5 Способов Повысить Производительность Кода C# Бесплатно
Разработка программного обеспечения - это поиск компромиссов:
- нормализация против денормализации в реляционных базах данных,
- скорость разработки против качественного кода
и т.п.
Высокопроизводительный код C# обычно требует компромиссов. Разработчики могут пожертвовать удобством сопровождения или безопасностью кода, чтобы код работал быстрее. Но это применимо только к сценариям, в которых уже применяются все шаблоны производительности и лучшие практики, а производительность всё равно требует дальнейшего улучшения.
Существует множество подходов, которые могут помочь разработчикам значительно улучшить производительность приложений, ничем не жертвуя.
Начнём с очевидного:
1. Указывайте ёмкость коллекции
Рассмотрим два почти идентичных метода:
public void NonFixedCapacityTest()Оба метода выполняют одну и ту же задачу - заполнение коллекции целыми числами с помощью цикла
{
var items = new List<decimal>();
for (int i = 0; i < 1000000; i++)
items.Add(i);
}
public void FixedCapacityTest()
{
const int capacity = 1000000;
var items = new List<decimal>(capacity);
for (int i = 0; i < capacity; i++)
items.Add(i);
}
foreach. Единственное отличие состоит в том, что в методе FixedCapacityTest конструктор коллекции инициализируется некоторым числом. Этот простой трюк заставляет метод FixedCapacityTest работать в два раза быстрее, чем NonFixedCapacityTest.| Method | Mean |Производительность в два-три раза выше, потому что
|--------------------- |----------:|
| NonFixedCapacityTest | 22.708 ms |
| FixedCapacityTest | 8.418 ms |
List<T> реализован таким образом, что хранит элементы в массиве, который представляет собой структуру данных фиксированного размера. Когда разработчик создает экземпляр List<T> без указания его ёмкости, выделяется массив ёмкости по умолчанию. Когда массив заполнен, выделяется новый массив большего размера, а значения из старого массива копируются в новый.Предварительное указание ёмкости устраняет накладные расходы на выделение, копирование и сборку мусора использованных массивов. Разработчики должны всегда указывать ёмкость коллекции, если они заранее знают, сколько элементов будет в неё добавлено.
Параметр ёмкости работает не только с коллекцией
List, но и с другими, такими как Dictionary<TKey, TValue>, HashSet<T> и т.п.Продолжение следует…
Источник: https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
День восемьсот сорок первый. #PerformanceTips
5 Способов Повысить Производительность Кода C# Бесплатно
2. Используйте структуры вместо классов в некоторых случаях
Разработчикам часто может потребоваться выделить массив или список для хранения десятков тысяч объектов в памяти. Эту задачу можно решить с помощью класса или структуры.
Продолжение следует…
Источник: https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da
5 Способов Повысить Производительность Кода C# Бесплатно
2. Используйте структуры вместо классов в некоторых случаях
Разработчикам часто может потребоваться выделить массив или список для хранения десятков тысяч объектов в памяти. Эту задачу можно решить с помощью класса или структуры.
public class PointClassКак видите, единственная разница между
{
public int X { get; set; }
public int Y { get; set; }
}
public struct PointStruct
{
public int X { get; set; }
public int Y { get; set; }
}
public void ListOfObjectsTest()
{
const int length = 1000000;
var items = new List<PointClass>(length);
for (int i = 0; i < length; i++)
items.Add(new PointClass() { X = i, Y = i });
}
public void ListOfStructsTest()
{
const int length = 1000000;
var items = new List<PointStruct>(length);
for (int i = 0; i < length; i++)
items.Add(new PointStruct() { X = i, Y = i});
}
ListOfObjectTest и ListOfStructsTest заключается в том, что первый создаёт экземпляры класса, а второй - экземпляры структур. Код PointClass идентичен коду PointStruct.| Method | Mean |Код, использующий структуры, работает в 10-15 раз быстрее, чем код, использующий классы. Такая большая разница во времени, объясняется тем, что в случае классов CLR должна выделить один миллион объектов в управляемой куче и сохранить ссылки на них в коллекции
|------------------ |----------:|
| ListOfObjectsTest | 67.724 ms |
| ListOfStructsTest | 5.136 ms |
List<T>. В случае структур единственным объектом, размещённым в куче, будет экземпляр коллекции List<T>. Миллион структур будет встроен в этот единственный экземпляр коллекции.Продолжение следует…
Источник: https://levelup.gitconnected.com/5-ways-to-improve-the-performance-of-c-code-for-free-c89188eba5da