Профилирование приложений .NET с помощью секундомера

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

* свободен как свобода, то есть лицензия включает коммерческие приложения.

EDIT: В ответ тем, кто сказал мне "купить профайлер", я бы хотел, но если бы я мог потратить столько денег, я бы потратил его на что-то еще. Я попытался убедить своего босса, что профилировщик этого стоит, но ему не повезло. Этот вопрос в основном основан на любопытстве. Я бы никогда не рассмотрел Секундомер в качестве замены реального профилировщика.

У меня есть небольшое тестовое приложение (написанное на С#), которое измеряет разницу в производительности при использовании секундомера для каждой строки. Код проверки:

int n = 100;
BigInteger f = 1;
for (int i = n; i > 1; i--)
{
    f *= i;
}

Вот полный код: http://pastebin.com/AvbQmT32

У меня есть секундомер для каждой строки кода. Это мой "профайлер". У меня также есть один секундомер для всей программы. Это мой "профилировщик профилировщика".

У меня есть программа, настроенная как режим деблокирования, любой процессор (на машине x64), а оптимизация отключена.

Когда я запускаю программу с отключенным профилировщиком, я получаю что-то вроде этого:

             Line             |  Ticks
------------------------------|----------
                              |
Total time:                   |       359

Когда я запускаю его с включенным профилировщиком, я получаю что-то вроде этого:

             Line             |  Ticks
------------------------------|----------
                              |
int n = 100;                  |         3
BigInteger f = 1;             |        12
for (int i = n; i > 1; i--)   |       325
{                             |
    f *= i;                   |       539
}                             |
                              |
Total time:                   |      1710
Stopwatch overhead:           |       831

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

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

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

Однако при использовании секундомеров все еще накладные расходы, особенно если вы используете много.

Итак, до вопроса:

Каков наиболее эффективный способ использования секундомеров для профилирования? Как можно минимизировать накладные расходы? Стоит ли даже переносить Секундомер вокруг одного утверждения?

Я ценю ваши отзывы.

Ответ 1

Первое дело: ваши результаты ничуть не удивляют. Если вы использовали коммерческий профайлер, вы увидите нечто похожее: вашей программе потребуется намного больше времени, чтобы работать, когда она профилируется, чем когда это не так. Чем более детализирован шаблон, тем дольше вы можете ожидать его. Когда вы считаете, что такие утверждения, как "i > 1" и "i--", скорее всего будут выполняться как инструкции с одним процессором, становится очевидным, почему профилирование времени выполнения конкретной строки может занять намного больше времени, чем выполнение самой строки.

Тот факт, что профилирование увеличивает общее время работы вашей программы, не должно вызывать беспокойства; как отмечали некоторые другие, важно не абсолютное время, в течение которого программа работает, а сравнение времени работы отдельных частей друг с другом, чтобы найти узкое место. Но есть еще одна проблема. Секундомер будет использовать высокочастотный таймер из базовой ОС, если он доступен; но даже это может быть недостаточно высоким. Высокочастотный таймер на моем 64-битном i5-2400 (четырехъядерный ядро ​​3,10 ГГц) Windows 7 гаснет 3 020 556 раз в секунду. Это звучит много; но с этой скоростью процессор мог выполнить тысячу инструкций между тиками. Это означает, что если вы пытаетесь измерить время, затрачиваемое на выполнение одной инструкции, вы либо собираетесь продвигаться, либо перебрасывать путь.

Вам лучше было бы профилировать на уровне метода. Даже тогда вы столкнетесь с проблемами частоты, особенно если у вас есть небольшие методы, основанные на хороших фактах. Но результаты будут более надежными, чем на уровне линии; и как только вы определили метод, вызывающий узкое место, вы можете проверить его непосредственно, чтобы определить, как оптимизировать его для лучшей работы.

Все это оставляет в стороне множество предостережений, которые идут вместе с профилированием производительности в целом, что выходит за рамки этой публикации. Удостоверьтесь, что вы проводите дополнительные исследования по этому предмету, чтобы понять, как вы должны интерпретировать все полученные результаты. В качестве краткого примера ваше профилирование может показать, что большая часть времени в вашей программе расходуется в определенном методе; но означает ли это, что сам метод является проблемой или что другие методы часто его называют? Ответ на этот вопрос заключается в том, где лежит реальная трудность в профилировании.

Ответ 2

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

Есть, естественно, случаи, когда вам также нужно иметь информацию. Но не используйте его во всех приложениях, а в одной части функции или одной функции. В этом случае StopWatch - лучший выбор. Учтите, что StopWatch равно .NET Class, поэтому он имеет минимальные издержки. Вы не должны смотреть на абсолютное, а на относительные значения.

Надеюсь, что это поможет.

Ответ 3

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

Если вы разрабатываете код на уровне, где производительность на самом деле является проблемой, и вам нужно профилировать его, чтобы найти код нарушения, то покупка инструмента для выполнения этой задачи для вас будет более чем оплачивать себя за время, сохраненное, Профилировщик, который поставляется с Visual Studio Premium, является тем, что я бы рекомендовал.

Ответ 4

Вы пробовали посмотреть на CInject: http://codeinject.codeplex.com/documentation? Он в основном вводит код в DLL. Из коробки, похоже, есть мониторинг производительности, но он также позволяет вам создать свой собственный код для ввода. Кажется идеальным для того, что вы пытаетесь сделать.

Ответ 5

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

Однако, несмотря на то, что секундомер имеет высокое разрешение, я бы подумал, что вы заметите, что некоторая временная ошибка измеряет все, что меньше одной строки кода. Например, "int n = 100" - это одна строка в сборке, поэтому в технических терминах я бы подумал, что это будет всего 1 тик. Сколько изменений вы заметили в своих номерах от запуска до запуска? Если вы получаете вариацию, которая является значительным процентом от вашей средней стоимости, то эта ценность действительно не дает вам такой информации.

Ответ 6

Возможно, вам следует подумать в терминах времени вызова метода профилирования, а не отдельных строк. В конце концов, вызов метода обычно представляет собой одну строку в вызывающем коде. Также интересным является использование библиотеки журналов для входа и выхода метода журнала, например, как это (используя log4net в качестве регистратора выбора ):

public string MyMethod(string arg)
{
    string result = null;

    if (logger.IsDebugEnabled) logger.Debug("->MyMethod(" + arg + ")");

    // do stuff
    result = "Hello world!";

    if (logger.IsDebugEnabled) logger.Debug("<-MyMethod(" + arg + ") = " + result);

    return result;
}

Это создаст файл журнала, который действительно полезен для просмотра того, что делает ваше приложение, и имеет бонус отметки времени каждой строки, записанной в журнал, чтобы вы могли видеть, что какой-либо конкретный метод занимает много времени. С помощью log4net вы можете легко изменить конфигурацию, чтобы отключить ведение журнала, поэтому, когда вам это не нужно, вы не платите никаких штрафных санкций.

Ответ 7

Если у вас Visual Studio Ultimate, попробуйте использовать Visual Studio Profiler. Это довольно хорошо.