Советы по оптимизации программ С#/. NET
Кажется, что оптимизация - это потерянное искусство в наши дни. Разве не было времени, когда все программисты сжимали каждую унцию эффективности от своего кода? Часто это делают, ходя пять миль в снегу?
В духе возврата утраченного искусства, какие советы вы знаете для простых (или, возможно, сложных) изменений для оптимизации кода С#/. NET? Поскольку такая широкая вещь, которая зависит от того, что она пытается выполнить, поможет обеспечить контекст вашим советом. Например:
- При объединении многих строк вместо этого используйте
StringBuilder
. См. Ссылку внизу для предупреждения об этом.
- Используйте
string.Compare
для сравнения двух строк вместо того, чтобы делать что-то вроде string1.ToLower() == string2.ToLower()
Общим консенсусом, по-видимому, является измерение. Этот вид пропускает точку: измерение не говорит вам, что неправильно, или что с этим делать, если вы столкнетесь с узким местом. Однажды я столкнулся с узким местом конкатенации строк и понятия не имел, что с этим делать, поэтому эти советы полезны.
Моя точка даже для того, чтобы разместить это, - это место для общих узких мест и того, как их можно избежать, даже не столкнувшись с ними. Это даже не обязательно о коде подключи и играй, в котором каждый должен слепо следовать, но больше о том, чтобы понять, что производительность должна восприниматься, по крайней мере, несколько, и что есть некоторые общие проблемы, которые нужно искать.
Я вижу, что было бы полезно знать, почему отзыв полезен и где он должен применяться. Для наконечника StringBuilder
я нашел помощь, которую я сделал давно, на здесь, на сайте Jon Skeet.
Ответы
Ответ 1
Кажется, что оптимизация - это потерянное искусство в наши дни.
Был один раз в день, когда производство, скажем, микроскопов осуществлялось как искусство. Оптические принципы были плохо поняты. Не было стандартизации деталей. Трубы и шестерни и линзы должны были быть сделаны вручную высококвалифицированными рабочими.
В наши дни микроскопы производятся как инженерная дисциплина. Основополагающие принципы физики чрезвычайно понятны, готовые детали широко доступны, и инженеры-разработчики микроскопов могут сделать осознанный выбор того, как наилучшим образом оптимизировать свой инструмент для задач, которые он предназначен для выполнения.
Этот анализ производительности - "потерянное искусство" - очень и очень хорошая вещь. Это искусство практиковали как искусство. К оптимизации следует подходить для того, что это такое: инженерная проблема, решаемая путем тщательного применения твердых инженерных принципов.
На протяжении многих лет меня спрашивали за мой список "советов и советов", которые люди могут использовать для оптимизации своих vbscript/их jscript/их активных страниц сервера/их кода VB/их С#. Я всегда сопротивляюсь этому. Подчеркивание "советов и трюков" - это точно неправильный способ приблизиться к производительности.. Этот способ приводит к тому, что трудно понять, трудно понять, трудно поддерживать, что обычно не заметно быстрее, чем соответствующий простой код.
Правильный подход к производительности - это подход к ней как инженерная проблема, как любая другая проблема:
- Задайте целенаправленные, измеримые цели, ориентированные на клиента.
- Постройте тестовые комплекты, чтобы проверить свою эффективность против этих целей в реалистичных, но контролируемых и повторяемых условиях.
- Если эти пакеты показывают, что вы не отвечаете вашим целям, используйте инструменты, такие как профилировщики, чтобы понять, почему.
- Оптимизируйте черту того, что профайлер идентифицирует как наихудшую подсистему. Держите профилирование при каждом изменении, чтобы вы четко понимали влияние производительности каждого из них.
- Повторяйте до тех пор, пока не произойдет одно из трех событий: 1) вы достигнете своих целей и отправите программное обеспечение; (2) вы пересматриваете свои цели вниз к чему-то, что вы можете достичь, или (3) ваш проект отменен, потому что вы не могли встретить цели.
Это то же самое, что вы решите любую другую инженерную проблему, например, добавив функцию: задавайте цели, ориентированные на клиента для этой функции, отслеживайте прогресс в создании надежной реализации, исправляете проблемы, как вы их находите, путем тщательного анализа отладки, продолжайте итерацию до тех пор, пока вы не отправите или не сработаете. Производительность - это функция.
Анализ эффективности сложных современных систем требует дисциплины и сосредоточен на принципах твердой инженерии, а не на сумке, полной трюков, которые применимы к тривиальным или нереалистичным ситуациям. Я никогда не решал проблему производительности в реальном мире посредством применения советов и трюков.
Ответ 2
Получите хороший профилировщик.
Не стоит даже пытаться оптимизировать С# (действительно, любой код) без хорошего профилирования. На самом деле это помогает значительно упростить выборку и профилировщик трассировки.
Без хорошего профилировщика вы, вероятно, создадите ложную оптимизацию и, самое главное, оптимизируете подпрограммы, которые не являются проблемой производительности в первую очередь.
Первые три шага к профилированию всегда должны быть 1) Измерять, 2) измерять, а затем 3) измерять....
Ответ 3
Рекомендации по оптимизации:
- Не делайте этого, если вам не нужно
- Не делайте этого, если дешевле бросать новое оборудование в проблему вместо разработчика
- Не делайте этого, если вы не можете измерить изменения в производственной эквивалентной среде.
- Не делайте этого, если вы не знаете, как использовать CPU и профайлер памяти
- Не делайте этого, если он сделает ваш код нечитаемым или недостижимым
По мере того, как процессоры продолжают ускоряться, основным узким местом в большинстве приложений не является процессор, его пропускная способность: пропускная способность для внебиржевой памяти, пропускная способность на диск и пропускная способность сети.
Начните с дальнего конца: используйте YSlow, чтобы узнать, почему ваш веб-сайт работает медленно для конечных пользователей, а затем вернитесь назад и исправьте доступ к базе данных не слишком широко (столбцы) и не слишком глубокие (строки).
В очень редких случаях, когда стоит делать что-либо для оптимизации использования ЦП, будьте осторожны, чтобы вы не отрицательно влияли на использование памяти: я видел "оптимизации", когда разработчики пытались использовать память для кэширования результатов для экономии циклов процессора, Чистым эффектом было уменьшение доступной памяти для кэширования страниц и результатов базы данных, что заставило приложение работать намного медленнее! (См. Правило измерения).
Я также видел случаи, когда "немой" не оптимизированный алгоритм избил "умный" оптимизированный алгоритм. Никогда не недооценивайте, как хорошие компиляторы и разработчики чипов стали превращать "неэффективный" цикл кода в суперэффективный код, который может полностью работать в встроенной памяти с конвейерной обработкой. Ваш "умный" древовидный алгоритм с развернутым внутренним циклом, считая назад, который, по вашему мнению, был "эффективным", может быть избит просто потому, что он не смог остаться во встроенной памяти во время выполнения. (См. Правило измерения).
Ответ 4
Когда работа с ORM должна быть в курсе выбора N + 1.
List<Order> _orders = _repository.GetOrders(DateTime.Now);
foreach(var order in _orders)
{
Print(order.Customer.Name);
}
Если клиенты не загружены, это может привести к нескольким обратным поездкам в базу данных.
Ответ 5
- Не используйте магические числа, используйте перечисления
- Не указывайте значения жесткого кода
- Используйте генераторы, где это возможно, поскольку они набирают имя и избегают бокса и распаковки.
- Используйте обработчик ошибок, где он абсолютно необходим
- Утилизировать, распоряжаться, распоряжаться. CLR не знает, как закрыть соединения с базой данных, поэтому закройте их после использования и удалите неуправляемые ресурсы.
- Используйте здравый смысл!
Ответ 6
Хорошо, мне нужно бросить в мой любимый: если задача достаточно длинная для взаимодействия с человеком, используйте ручной разрыв в отладчике.
Vs. профилировщик, это дает вам стек вызовов и значения переменных, которые вы можете использовать, чтобы действительно понять, что происходит.
Сделайте это 10-20 раз, и вы получите представление о том, какая оптимизация может действительно повлиять.
Ответ 7
Если вы идентифицируете метод как узкое место, но вы не знаете, что с ним делать, вы по существу застреваете.
Итак, я перечислил несколько вещей. Все это не серебряные пули, и вам все равно придется профилировать свой код. Я просто делаю предложения о том, что вы могли бы сделать, и иногда может помочь. Особенно важны первые три.
- Попробуйте решить проблему, используя только (или: в основном) низкоуровневые типы или массивы из них.
- Проблемы часто бывают небольшими - использование интеллектуального, но сложного алгоритма не всегда заставляет вас побеждать, особенно если менее интеллектуальный алгоритм может быть выражен в коде, который использует только типы (типы массивов) низкого уровня. Возьмем, к примеру, метод InsertionSort vs MergeSort для n <= 100 или Tarjan Dominator vs с использованием битвекторов, чтобы наивно решить форму потока данных задачи для n <= 100. (100, конечно, просто для того, чтобы дать вам представление - профиль!)
- Рассмотрите возможность написания специального случая, который может быть разрешен с использованием только типов низкого уровня (часто проблемных экземпляров размера < 64), даже если вам нужно сохранить другой код для более крупных проблемных экземпляров.
- Изучите поразрядную арифметику, чтобы помочь вам с двумя идеями выше.
- BitArray может быть вашим другом, по сравнению с Словарем, или, что еще хуже, List. Но будьте осторожны, что реализация не оптимальна; Вы можете написать более быструю версию самостоятельно. Вместо того, чтобы тестировать, что ваши аргументы вне диапазона и т.д., Вы можете часто структурировать свой алгоритм так, чтобы индекс не мог выйти из диапазона в любом случае, но вы не можете удалить проверку из стандартного BitArray и это не бесплатно.
- В качестве примера того, что вы можете делать только с массивами типов низкого уровня, BitMatrix - довольно мощная структура, которая может быть реализована как просто массив ulongs, и вы можете даже пересечь ее, используя ulong как "front", потому что вы можете взять бит младшего порядка в постоянное время (по сравнению с очередью в первом поиске по ширине), но, очевидно, порядок отличается и зависит от индекса элементов, а не от того, в каком порядке вы их найдете).
- Разделение и модуляция действительно медленны, если правая часть не является константой.
- Математика с плавающей запятой не в целом медленнее, чем целая математика (не "что-то, что вы можете сделать", а "что-то, что вы можете пропустить" )
- Ветвление не является бесплатным. Если вы можете избежать этого с помощью простой арифметики (ничего, кроме деления или по модулю), вы можете получить некоторую производительность. Перемещение ветки во внешний цикл почти всегда является хорошей идеей.
Ответ 8
У людей есть смешные идеи о том, что на самом деле имеет значение. Qaru полон вопросов, например, ++i
больше "performant", чем i++
. Вот пример реальной настройки производительности, и это в основном та же процедура для любого языка. Если код просто написан определенным образом "потому что он быстрее", это угадывание.
Конечно, вы не намеренно пишете глупый код, но если угадывание сработало, не было бы необходимости в профайлерах и методах профилирования.
Ответ 9
Истина заключается в том, что нет идеального оптимизированного кода. Тем не менее, вы можете оптимизировать определенную часть кода на известной системе (или наборе систем) на известном типе (и счете) процессора, известной платформе (Microsoft? Mono?), известная версия /версия BCL, известная версия CLI, известный компилятор версия (ошибки, изменения спецификации, твики), известное количество общей и доступной памяти, известное происхождение сборки (GAC? disk? remote?), с известной активностью фоновой системы от других процессов.
В реальном мире используйте профайлер и посмотрите на важные биты; обычно очевидные вещи - это что-то вроде ввода-вывода, что-либо, связанное с потоковой обработкой (опять-таки, это сильно меняется между версиями) и все, что связано с циклами и поисками, но вы можете быть удивлены тем, что "явно плохой" код на самом деле не проблема, и какой "явно хороший" код является огромным виновником.
Ответ 10
Расскажите компилятору, что делать, а не как это сделать. В качестве примера foreach (var item in list)
лучше, чем for (int i = 0; i < list.Count; i++)
, а m = list.Max(i => i.value);
лучше, чем list.Sort(i => i.value); m = list[list.Count - 1];
.
Рассказывая системе, что вы хотите сделать, она может найти лучший способ сделать это. LINQ хорош, потому что его результаты не вычисляются до тех пор, пока они вам не понадобятся. Если вы используете только первый результат, ему не нужно вычислять остальные.
В конечном счете (и это относится ко всему программированию) минимизируйте циклы и минимизируйте то, что вы делаете в циклах. Еще более важно минимизировать количество циклов внутри ваших петель. Какая разница между алгоритмом O (n) и алгоритмом O (n ^ 2)? Алгоритм O (n ^ 2) имеет петлю внутри цикла.
Ответ 11
Я действительно не пытаюсь оптимизировать свой код, но порой я буду использовать и использовать что-то вроде рефлектора, чтобы вернуть мои программы в исходный код. Интересно затем сравнить то, что я не так, с выходом отражателя. Иногда я нахожу, что то, что я делал в более сложной форме, было упрощено. Невозможно оптимизировать ситуацию, но помочь мне увидеть более простые решения проблем.