Достаточно часто на SO я обнаруживаю, что тестирую небольшие фрагменты кода, чтобы увидеть, какая реализация является самой быстрой.
Довольно часто я вижу комментарии о том, что код тестирования не учитывает джиттинг или сборщик мусора.
У меня есть следующая простая функция тестирования, которую я медленно развивал:
static void Profile(string description, int iterations, Action func) {
// warm up
func();
// clean up
GC.Collect();
var watch = new Stopwatch();
watch.Start();
for (int i = 0; i < iterations; i++) {
func();
}
watch.Stop();
Console.Write(description);
Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
}
Использование:
Profile("a descriptions", how_many_iterations_to_run, () =>
{
// ... code being profiled
});
Есть ли в этой реализации недостатки? Достаточно ли хорошо, чтобы показать, что реализация X быстрее, чем реализация Y по Z итерациям? Можете ли вы придумать, как бы это улучшить?
РЕДАКТИРОВАТЬ Совершенно очевидно, что предпочтительнее использовать подход, основанный на времени (в отличие от итераций), есть ли у кого-нибудь реализации, в которых проверки времени не влияют на производительность?
c#
.net
performance
profiling
Сэм Шафран
источник
источник
Ответы:
Вот модифицированная функция: как рекомендовано сообществом, не стесняйтесь вносить поправки в это вики сообщества.
Убедитесь, что вы компилируете Release с включенной оптимизацией, и запускаете тесты вне Visual Studio . Эта последняя часть важна, потому что JIT ограничивает свою оптимизацию подключенным отладчиком даже в режиме Release.
источник
Доработка не обязательно будет завершена раньше
GC.Collect
возврата. Завершение ставится в очередь, а затем выполняется в отдельном потоке. Эта ветка может оставаться активной во время ваших тестов, что влияет на результаты.Если вы хотите убедиться, что финализация завершена перед запуском ваших тестов, вы можете вызвать вызов
GC.WaitForPendingFinalizers
, который будет заблокирован до тех пор, пока очередь финализации не будет очищена:источник
GC.Collect()
еще раз?Collect
нужен, чтобы убедиться, что «завершенные» объекты также собраны.Если вы хотите исключить из уравнения взаимодействия с GC, вы можете запустить свой «разогревающий» вызов после вызова GC.Collect, а не до него. Таким образом, вы знаете, что .NET уже будет иметь достаточно памяти, выделенной из ОС для рабочего набора вашей функции.
Помните, что вы выполняете вызов метода без встроенного кода для каждой итерации, поэтому убедитесь, что вы сравниваете то, что вы тестируете, с пустым телом. Вам также придется признать, что вы можете надежно рассчитать время только для вещей, которые в несколько раз длиннее, чем вызов метода.
Кроме того, в зависимости от того, какие данные вы профилируете, вы можете захотеть выполнить работу на основе времени в течение определенного времени, а не определенного количества итераций - это может привести к более легко сопоставимым числам без необходимость иметь очень короткий пробег для лучшей реализации и / или очень долгий для худшего.
источник
Я бы вообще не передавал делегата:
Пример кода, приводящего к использованию закрытия:
Если вы не знаете о замыканиях, взгляните на этот метод в .NET Reflector.
источник
IDisposable
.Я думаю, что самая сложная проблема, которую можно решить с помощью таких методов тестирования, - это учет крайних случаев и непредвиденных обстоятельств. Например - «Как два фрагмента кода работают при высокой загрузке ЦП / использовании сети / перегрузке диска и т. Д.». Они отлично подходят для базовой логической проверки, чтобы увидеть, работает ли конкретный алгоритм значительно быстрее, чем другой. Но чтобы правильно протестировать производительность большей части кода, вам нужно будет создать тест, который измеряет конкретные узкие места этого конкретного кода.
Я бы по-прежнему сказал, что тестирование небольших блоков кода часто мало окупается и может поощрять использование слишком сложного кода вместо простого поддерживаемого кода. Написание четкого кода, который другие разработчики или я через 6 месяцев могу быстро понять, даст больше преимуществ в производительности, чем высоко оптимизированный код.
источник
Я звонил
func()
на разминку несколько раз, а не один.источник
Предложения по улучшению
Определение того, подходит ли среда выполнения для тестирования (например, определение того, подключен ли отладчик или отключена ли оптимизация jit, что может привести к неверным измерениям).
Независимое измерение частей кода (чтобы точно увидеть, где находится узкое место).
Что касается №1:
Чтобы определить, подключен ли отладчик, прочтите свойство
System.Diagnostics.Debugger.IsAttached
(не забудьте также обработать случай, когда отладчик изначально не подключен, но подключается через некоторое время).Чтобы определить, отключена ли оптимизация jit, прочтите свойство
DebuggableAttribute.IsJITOptimizerDisabled
соответствующих сборок:Что касается №2:
Это можно сделать разными способами. Один из способов - предоставить несколько делегатов, а затем измерить их по отдельности.
Что касается №3:
Это также можно было сделать разными способами, и разные сценарии использования потребовали бы очень разных решений. Если тест запускается вручную, запись в консоль может быть хорошей. Однако, если тест выполняется автоматически системой сборки, то запись в консоль, вероятно, не так хороша.
Один из способов сделать это - вернуть результат теста в виде строго типизированного объекта, который можно легко использовать в различных контекстах.
Etimo.
Другой подход - использовать существующий компонент для выполнения тестов. Фактически, в моей компании мы решили выпустить наш тестовый инструмент в общественное достояние. По сути, он управляет сборщиком мусора, джиттером, разогревом и т.д., как и предлагают некоторые другие ответы здесь. Он также имеет три функции, которые я предложил выше. Он управляет несколькими проблемами, обсуждаемыми в блоге Эрика Липперта .
Это пример вывода, в котором сравниваются два компонента, и результаты записываются в консоль. В этом случае два сравниваемых компонента называются KeyedCollection и MultiplyIndexedKeyedCollection:
Существует пакет NuGet , образец пакета NuGet, а исходный код доступен на GitHub. . Также есть сообщение в блоге .
Если вы торопитесь, я предлагаю вам получить образец пакета и просто изменить образцы делегатов по мере необходимости. Если вы никуда не торопитесь, возможно, стоит прочитать сообщение в блоге, чтобы понять подробности.
источник
Вы также должны выполнить «прогрев» перед фактическим измерением, чтобы исключить время, которое JIT-компилятор тратит на изменение вашего кода.
источник
В зависимости от кода, который вы тестируете, и платформы, на которой он работает, вам может потребоваться учитывать, как выравнивание кода влияет на производительность . Для этого, вероятно, потребуется внешняя оболочка, которая запускала бы тест несколько раз (в отдельных доменах приложений или процессах?), В некоторых случаях сначала вызывая «код заполнения», чтобы заставить его быть скомпилированным JIT, чтобы код был оценивается, чтобы быть выровненным по-другому. Полный результат теста даст наилучшее и наихудшее время для различных выравниваний кода.
источник
Если вы пытаетесь устранить влияние сборки мусора после завершения теста, стоит ли его устанавливать
GCSettings.LatencyMode
?Если нет, и вы хотите, чтобы влияние мусора, созданного в,
func
было частью теста, то не следует ли вам также принудительно выполнять сборку в конце теста (внутри таймера)?источник
Основная проблема с вашим вопросом заключается в предположении, что одно измерение может ответить на все ваши вопросы. Чтобы получить эффективную картину ситуации, необходимо проводить измерения несколько раз, особенно на языке, на котором выполняется сборка мусора, например C #.
Другой ответ дает хороший способ измерить базовую производительность.
Однако это единственное измерение не учитывает сборку мусора. Правильный профиль дополнительно учитывает наихудшую производительность сборки мусора, распределенного по множеству вызовов (это число бесполезно, поскольку виртуальная машина может завершить работу, даже не собирая оставшийся мусор, но все же полезно для сравнения двух разных реализаций
func
.)И можно также измерить производительность сборки мусора в наихудшем случае для метода, который вызывается только один раз.
Но более важной, чем рекомендация каких-либо конкретных возможных дополнительных измерений для профиля, является идея о том, что нужно измерять несколько различных статистических данных, а не только один вид статистики.
источник