Так как при дискретном мониторинге требуется, чтобы поток выполнения в приложении был активен в моменты сбора информации, нет никакого способа получить информацию о потоках, простаивавших в ожидании выполнения операций ввода/вывода или на блокировках механизмов синхронизации. Дискретный мониторинг отлично подходит для анализа приложений, занимающихся преимущественно вычислениями, но для приложений, основная работа которых заключается в выполнении операций ввода/вывода, необходимы иные подходы, опирающиеся на использование других, более глубинных механизмов профилирования.
Он позволяет измерить общее время выполнения, а не только процессорное время, хорошо подходит для профилирования приложений, выполняющих большое количество операций ввода/вывода или интенсивно использующих механизмы синхронизации.
В режиме инструментированного профилирования профилировщик изменяет целевой выполняемый файл и внедряет в него код, выполняющий измерения и сообщающий профилировщику точную информацию о времени выполнения и количестве вызовов каждого оцениваемого таким способом метода.
Инструментированный профилировщик генерирует отчет с представлением Summary (Сводка), содержащим информацию о наиболее затратных ветвях в стеке вызовов и отдельных функциях, на выполнение которых тратится больше всего времени. Информация в отчете будет основана на точном хронометраже, проводимом инструментированным кодом.
На рисунке ниже показано представление Functions, где доступны включительное и исключительное время, измеренные в миллисекундах, а также счетчики вызовов функций:
Рисунок 5. Результаты инструментированного профилирования из представления Functions
Инструментированное профилирование выглядит более точным, но на практике рекомендуется использовать дискретное профилирование, если приложение в основном решает вычислительные задачи. Прием инструментирования имеет ограниченную гибкость из-за необходимости изменять выполняемый файл приложения перед запуском и невозможности подключить профилировщик к уже запущенному процессу. Кроме того, инструментированное профилирование имеет немалые накладные расходы - объем выполняемого кода существенно увеличивается и в процессе выполнения часть времени затрачивается на отбор информации в точках входа и выхода из методов.
Как всегда, будет ошибкой безоговорочно доверять результатам инструментированного профилирования. Разумеется, количество вызовов того или иного метода не изменится из-за того, что приложение подвергается инструментированному профилированию, но информация о времени все еще может существенно искажаться из-за накладных расходов, несмотря на все попытки профилировщика сместить все дорогостоящие вычисления в конец. При внимательном подходе, дискретное и инструментированное профилирование могут помочь понять, где приложение проводит больше всего времени, особенно если сопоставлять множество отчетов и обращать внимание на результаты, полученные в результате оптимизации.
Профилировщик Visual Studio способен собирать информацию об операциях выделения памяти и жизненном цикле объектов (которые освобождаются сборщиком мусора) в обоих режимах, дискретном и инструментированном. В дискретном режиме профилировщик собирает информацию о выделении памяти в приложении в целом. В инструментированном режиме информация собирается только из инструментированных модулей.
Для того, чтобы запустить профилировщик выделения памяти, в мастере настройки профилировщика производительности нужно выбрать радиокнопку .NET memory allocation (Выделение памяти .NET). В конце сеанса профилирования, в представлении Summary (Сводка), будут показаны функции, выделившие памяти больше всего:
Рисунок 6. Результаты профилирования выделения памяти из представления Summar
В представлении Functions для каждого метода будет указано количество объектов и количество байтов памяти, выделенных методом (как обычно, включительные и исключительные значения). В представлении Function Details (Сведения о функции) будет представлена информация о вызывающих и вызываемых функциях, а также указаны строки кода с объемами выделенной ими памяти в поле слева:
Рисунок 7. Результаты профилирования выделения памяти из представления Function Details
Но самая интересная информация содержится в представлении Allocation (Выделение), показывающем, какие ветви в стеке вызовов выделили памяти больше всего:
Рисунок 8. Результаты профилирования выделения памяти из представления Allocation
Представление Object Lifetime (Жизненный цикл объектов), сообщает, в каком поколении объекты были утилизированы. Это представление поможет увидеть, имеются ли объекты, пережившие слишком много циклов сборки мусора. На рисунке ниже можно видеть, что все объекты строк, созданные приложением (и занимающие более 1 Гбайта памяти!) были утилизированы в нулевом поколении, а это означает, что ни одному из них не удалось прожить дольше одного цикла сборки мусора:
Рисунок 9. Результаты профилирования выделения памяти из представления Object Lifetime
Профилировщик Visual Studio, в режимах Concurrency (Данные конфликтов ресурсов (параллелизм)) и Concurrency Visualizer (Визуализатор параллелизма), использует события ETW для мониторинга производительности многопоточных приложений и предоставляет отчет с несколькими весьма полезными представлениями, упрощающими узкие места, отрицательно сказывающиеся на масштабируемости и производительности. Он имеет два режима работы.
В режиме Concurrency (Данные конфликтов ресурсов (параллелизм)) выявляются ресурсы, такие как управляемые блокировки, на которых потоки выполнения в приложении останавливаются в ожидании их освобождения. Первая часть отчета описывает сами ресурсы и потоки выполнения, которые были заблокированы на них - она поможет найти и устранить недостатки, мешающие масштабированию:
Рисунок 10. Результаты профилирования конкуренции из представления Resource Details
Как видно на рисунке, существует несколько потоков выполнения, ожидающих доступа к ресурсу. При выборе потока выполнения, в нижней части отображается его стек вызовов.
Другая часть отчета отображает информацию о конфликтах для конкретного потока выполнения, то есть перечень различных механизмов синхронизации, на которых поток выполнения вынужден был простаивать - она поможет уменьшить задержки на пути выполнения определенного потока.
Чтобы запустить профилировщик в этом режиме, нужно выбрать пункт меню Analyze --> Launch Performance Wizard (Анализ --> Запустить мастер производительности) и затем выбрать радиокнопку Concurrency (Данные конфликтов ресурсов (параллелизм)).
Рисунок 11. Сводная информация по потокам в режиме визуализация параллелизма
На основании этого отчета можно заключить, что работа распределяется между потоками неравномерно.
В режиме визуализации параллелизма отображается график выполнения всех потоков приложения, где цветом обозначается текущее состояние. Каждый поток имеет несколько переходных состояний - блокировка на операции ввода/вывода, ожидание на механизме синхронизации, выполнение - которые фиксируются профилировщиком, а также стек вызовов. Эти отчеты очень удобно использовать, чтобы понять особенности работы потоков выполнения и выявить причины низкой производительности, такие как чрезмерное количество потоков выполнения, недостаточное количество потоков выполнения и чрезмерное использование механизмов синхронизации.
В графике имеется также встроенная поддержка механизмов Task Parallel Library, таких как параллельные циклы (parallel loops) и механизмы синхронизации CLR. Чтобы запустить профилировщик в этом режиме выберите нужный пункт в подменю Analyze --> Concurrency Visualizer (Анализ --> Визуализатор параллелизма).
Написать многопоточную программу в соответствии с вариантом (количество потоков должно соответствовать возможному количеству потоков для вычислительной машины).
Провести профилирование с помощью дискретного профилировщика. В отчет занести результаты из представлений Summary, Functions. Для функций, на выполнение которых затрачено больше всего процессорного времени, добавить в отчет информацию из представления Function Details. Для данных в каждом из этих представлений написать комментарии.
Провести профилирование с помощью инструментированного профилировщика. В отчет занести результаты из представлений Summary, Functions. Для функций, у которых наибольшее исключительное время, добавить в отчет информацию из представления Function Details. Для данных в каждом из этих представлений написать комментарии.
Провести профилирование с помощью профилировщика выделения памяти. В отчет занести результаты из представлений Summary, Functions. Для функций, которые выделили больше всего памяти (исключительное значение), добавить в отчет информацию из представления Function Details. Также в отчет включить информацию из представления Allocation, а именно какие ветви в стеке вызовов выделили памяти больше всего. Для данных в каждом из этих представлений написать комментарии.
Провести профилирование с помощью профилировщика конкуренции. Для режима Concurrency в отчет занести результаты из представления Resource Details, Threads Details. Для режима Concurrency Visualizer – сводную информацию. Добавить комментарии.
На основании полученной информации сделать вывод о причинах временных затрат, большого выделения памяти и т.п. при выполнении программы. Можно собрать информацию и о других событиях, если это требуется для обоснования вывода.
Варианты заданий:
Разложение каждого числа от 1 до N на простые множетели.
Перемножение N матриц размерности MxM.
Свой вариант задания, предварительно согласовав его с преподавателем.
Вспомогательные материалы:
https://habrahabr.ru/post/97817/
http://professorweb.ru/my/csharp/optimization/level1/1_5.php
http://www.ishodniki.ru/art/art_progr/net/493.html
Ссылки:
https://professorweb.ru/my/csharp/optimization/level1/1_4.php
http://www.protesting.ru/testing/types/loadtesttypes.html
https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%81%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%BE%D0%B8%D0%B7%D0%B2%D0%BE%D0%B4%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8
https://habrahabr.ru/post/98361/
https://blogs.msdn.microsoft.com/developer-tools-rus/2013/04/23/143/