Профилирование приложений в Visual Studio 2010
Сегодня мы будем замерять производительность нашего приложения с помощью Visual Studio Profiling Tool.
Visual Studio Profiling Tool позволяет разработчикам измерять, оценивать производительность приложения и кода. Эти инструменты полностью встроены в IDE, чтобы предоставить разработчику беспрерывный контроль.
В этом руководстве мы по шагам профилируем приложение PeopleTrax используя Sampling и Instrumentation методы профилирования, чтобы выявить проблемы в производительности приложения.
Много картинок.
Подготовка
Для работы с этим руководством вам потребуется:
- Microsoft Visual Studio 2010
- Средние знания языка C#
- Копия тестового приложения PeopleTrax, скачать можно с MSDN Code Gallery
Методы профилирования
Чуть-чуть отступим от главной темы статьи и рассмотрим возможные методы профилирования. Эту главу можно пропустить, используемые методы профилирования будут кратко описаны перед использованием.
Sampling
Sampling — собирает статистические данные о работе приложения (во время профилирования). Этот метод легковесный и поэтому, в результате его работы очень маленькая погрешность в полученных данных.
Каждый определенный интервал времени собирается информация о стеке вызовов (call stack). На основе этих данные производится подсчет производительности. Используется для первоначального профилирования и для определения проблем связанных с использование процессора.
Instrumentation
Instrumentation — собирает детализированную информацию о времени работы каждой вызванной функции. Используется для замера производительности операций ввода/вывода.
Метод внедряет свой код в двоичный файл, который фиксирует информацию о синхронизации (времени) для каждой функции в файл, и для каждой функции которые вызываются в этой.
Отчет содержит 4 значения для предоставления затраченного времени:
- Elapsed Inclusive — общее время, затраченное на выполнение функции
- Application Inclusive — время, затраченное на выполнение функции, за исключением времени обращений к операционной системе.
- Elapsed Exclusive — время, затраченное на выполнение кода в теле. Время, которое тратят функции, вызванные целевой функцией.
- Application Exclusive — время, затраченное на выполнение кода в теле. Исключается время, которое тратится выполнения вызовов операционной системы и время, затраченное на выполнение функций, вызванные целевой функцией.
Concurrency
Concurrency – собирает информацию о многопоточных приложения (как отлаживать многопоточные приложения см. «Руководство по отладке многопоточных приложений в Visual Studio 2010»). Метод собирает подробную информацию о стеке вызовов, каждый раз, когда конкурирующие потоки вынуждены ждать доступа к ресурсу.
.NET Memory
.NET Memory — профайлер собирает информацию о типе, размере, а также количество объектов, которые были созданы в распределении или были уничтожены сборщиком мусора. Профилирование памяти почти не влияет на производительность приложения в целом.
Tier Interaction
Tier Interaction – добавляет информацию в файл для профилирования о синхронных вызовах ADO.NET между страницей ASP.NET или другими приложениями и SQL сервера. Данные включают число и время вызовов, а также максимальное и минимальное время.
На этом рассмотрение методов профилирование закончим и продолжим учиться профилировать приложения.
Профилирование Sampling методом
Sampling это метод профилирования, который периодически опрашивает рассматриваемый процесс, чтобы определить активную функцию. В результате показывает количество раз, когда функция была в начале call stack во время тестирования.
Профилирование
Открываем тестовый проект PeopleTrax. Устанавливаем конфигурацию в Release (в Debug версию встраивается дополнительная информация для отладки приложения, и она плохо скажется на точности результатов профилирования).
В меню Analyze нажимаем на Launch Performance Wizard.
На этом шаге нужно выбрать метод профилирования. Выбираем CPU Sampling (recommended) и нажимаем Next.
Выбираем какое приложение мы будем профилировать, это PeopleTrax и кнопка Next. В следующем нажимаем Finish и автоматически запустится профайлер и наше приложение. На экране мы видим программу PeopleTrax. Нажимаем кнопку Get People, ждем завершения работы и Export Data. Закрываем блокнот и программу и профайлер сгенерирует отчет.
Профайлер сгенерировал отчет (*.vsp)
Анализ отчета Sampling метода
В Summary отображается график использования процессора в течение всего времени профилирования. Список Hot Path показывает ветки вызовов, которые проявили наибольшую активность. А в списке Functions Doing Most Individual Work (название которого говорит само за себя) – функции, которые занимали большее время процесса в теле этих функций.
Посмотрев на список Hot Path видим что метод PeopleNS.People.GetNames занимает почти последнее место в ветке вызовов. Его то и можно изучить внимательнее на предмет улучшения производительности. Нажимаем на PeopleNS.People.GetNames и перед нами открывается Function Details.
Это окно содержит две части. Окно расходов предусматривает графическое представление работы функций, и вклад функции и вызывающих ее на количество экземпляров, которые были отобраны. Можно изменить рассматриваемую функцию, нажав на нее мышкой.
Function Code View показывает код метода, когда он доступен и подсвечивает наиболее «дорогие» строки в выбранном методе. Когда выбран метод GetNames видно, что он читает строки из ресурсов приложения используя StringReader, добавляя каждую строку в ArrayList. Нет очевидных способов улучшить эту часть.
Так как PeopleNS.People.GetPeople единственный, кто вызывает GetNames – нажимаем GetPeople. Этот метод возвращает ArrayList объектов PersonInformationNS.PersonInformation с именами людей и компаний, возвращенными методом GetNames. Тем не менее, GetNames вызывается дважды каждый раз, когда создается PersonInformation. (Это и показано желтым и красным выделением). Очевидно, что можно легко оптимизировать метод, создавая списки только один раз вначале метода.
Альтернативная версия GetPeople также есть в коде и мы ее сейчас включим. Для этого нужно определить OPTIMIZED_GETPEOPLE как Conditional compilation symbol в окне свойств проекта People и PeopleTrax. И да, если захотите повторить мои опыты, то нужно исправить ошибку в проекте. В оптимизированном конструкторе класса не правильно написано имя ресурсов: нужно PeopleNS.Resources вместе PeopleNS.Resource. Если это не изменить, все валится со страшными ошибками.
Оптимизированный метод заменит старый при следующей сборке.
Перезапускаем профилирование в текущей сессии нажав Launch with Profiling в окне Performance Explorer. Нажимаем на Get People и Export Data. Закрываем блокнот и программу а профайлер сгенерирует новый отчет.
Чтобы сравнить два отчета – выбираем оба и ПКМ Compare Performance Reports. Колонка дельты показывает разницу в производительности версии Baseline с более поздней Comparison. Выбираем Inclusive Samples % и Apply.
Как видно выигрыш в производительности заметен невооруженным глазом
Профилирование методом Instrumentation
Этот метод полезен при профилировании операций ввода вывода, запись на диск и при обмене данными по сети. Этот метод предоставляет больше информации чем предыдущий, но он несет с собой больше накладных расходов. Бинарники полученные после вставки дополнительного кода получаются больше обычных, и не предназначены для развертывания.
В этот раз мы сосредоточим наш анализ на экспорте данных, в котором список людей записывается в файл блокнота.
Профилирование
В Performance Explorer выбираем Instrumentation и нажмаем Start Profiling. Нажимаем Get People. После загрузки людей ждем 10 секунд и нажмаем Export Data. Закрываем блокнот и программу. Профилировщик сгенерирует отчет.
Анализ
Профилировщик покажет такую картинку:
Мы не получили ту информацию, которую хотели. Отфильтруем данные. Мы специально ждали 10 секунд, чтобы просто отфильтровать ненужные сейчас данные профилирования. Отмечаем с 13-й до конца и нажимаем Filter by selection. Уже другой результат:
Hot Path показывает, что метод Concat занимает много времени (он также первый в списке Functions With Most Individual Work). Нажимаем на Concat, чтобы посмотреть детально информацию о методе.
Видно, что PeopleTrax.Form1.ExportData – единственный метод, который вызывает Concat. Нажимаем PeopleTrax.Form1.ExportData в вызывающих методах (Function calling this function).
Анализируем метод в окне кода. Обратите внимание, что нет прямого вызова Concat. Вместе этого есть использование операнда +=, который компилятор заменяет на методы System.String.Concat. Как уже почти все знают, что любые изменения в строках в .NET приводят к уничтожению старой версии строки и созданию измененной строки. К счастью в .NET есть класс StringBuilder который и предназначен для такой работы.
В проекте уже есть оптимизированный метод с использованием StringBuilder. В проекте PeopleTrax добавляем переменную компиляции OPTIMIZED_EXPORTDATA. Сохраняем и снова запускаем профайлер и сравниваем отчеты. Сразу видно (да и логически понятно) что мы оптимизировали вызовы Concat (с 6000 до 0 раз).
После запуска приложения на глаз видно заметное улучшение производительности. Очень важно запускать профилирование еще раз, даже есть видимые улучшения. Просмотр новых данных после исправления проблемы может показать другие проблемы в производительности приложений.
Литература
- Getting Started with Profiling Tools
- Walkthrough: Profiling Applications
- PeopleTrax Sample (проект)
- Visual Studio Profiler Team Blog
- Руководство по отладке многопоточных приложений в Visual Studio 2010
Спасибо за внимание. Быстрого как молния вам кода.