Visual studio отладка памяти
С помощью встроенного в отладчик средства диагностики Использование памяти вы сможете находить утечки памяти и выявлять ее неэффективное использование. С помощью средства "Использование памяти" можно сделать один или несколько снимков управляемой и собственной памяти в куче, чтобы понять влияние использования памяти типов объектов. Анализировать использование памяти также можно без подключения отладчика — нужно просто указать выполняющееся приложение. Дополнительные сведения см. в разделе Запуск средств профилирования с отладчиком или без него.
Хотя с помощью средства Использование памяти можно делать снимки памяти в любой момент, для управления выполнением приложения во время анализа ошибок производительности вы можете использовать отладчик Visual Studio. Задание точек останова, пошаговое выполнение, всеобщее прерывание и другие действия отладчика могут помочь вам сосредоточиться на анализе производительности при обращении к наиболее важным ветвям кода. Выполняя эти действия, когда приложение запущено, вы сможете исключить влияние не интересующего вас кода и значительно ускорить диагностику проблем.
В этом руководстве рассмотрены следующие задачи:
- Создание моментальных снимков с данными об использовании памяти
- Анализ данных использования памяти
Если средство Использование памяти не предоставляет необходимые данные, можно воспользоваться другими средствами профилирования в Профилировщике производительности, предоставляющими другие виды информации, которая может оказаться полезной. Как правило, проблемы производительности приложения могут вызываться другими компонентами помимо памяти, такими как ЦП, отрисовка пользовательского интерфейса или время запроса сети.
Поддержка пользовательского распределителя. Профилировщик внутренней памяти работает путем сбора данных событий ETW выделения памяти, создаваемых во время выполнения. Распределители в CRT и пакете Windows SDK аннотированы на уровне исходного кода, что позволяет регистрировать их данные выделения. Если вы создаете собственные распределители, любые функции, возвращающие указатель на только что выделенную память в куче, можно декорировать с использованием __declspec(allocator), как показано в этом примере для myMalloc:
__declspec(allocator) void* myMalloc(size_t size)
Сбор данных об использовании памяти
Откройте проект для отладки в Visual Studio и установите точку останова в приложении в точке, где вы хотите начать проверку использования памяти.
Если вы подозреваете, что в определенной области памяти может возникнуть проблема, задайте первую точку останова до ее возникновения.
Так как из-за изменений в объеме выделяемой памяти создание профиля памяти для интересующей вас операции может быть затруднительно, разместите точки останова в начале и в конце операции или пройдите по ней, чтобы попробовать найти точку, в которой объем памяти изменился.
Установите вторую точку останова в конце функции или области кода, который требуется проанализировать, либо после возникновения предполагаемой проблемы с памятью.
Окно Средства диагностики появится автоматически, если вы не отключали эту функцию. Чтобы снова открыть окно, щелкните Отладка > Окна > Показать средства диагностики.
На панели инструментов выберите Использование памяти, применяя параметр Выбор средств.
Щелкните Отладка | Начать отладку (Запустить на панели инструментов или F5).
По завершении загрузки приложения отображается представление "Сводка" средств диагностики.
Поскольку сбор данных об использовании памяти может повлиять на производительность отладки приложений, основанных на машинном коде, а также смешанных программ, по умолчанию снимки памяти выключены. Чтобы включить моментальные снимки для приложений на базе машинного кода или для смешанных программ, начните сеанс отладки (клавиша: F5). Когда отобразится окно Средства диагностики, перейдите на вкладку Использование памяти и выберите Профилирование кучи.
Остановите (сочетание клавиш: SHIFT+F5) и перезапустите отладку.
Чтобы сделать моментальный снимок в начале сеанса отладки, на сокращенной панели инструментов Использование памяти выберите команду Сделать снимок. (Таким образом здесь также можно задать точку останова.)
Чтобы получить базовые показатели для сравнения состояния памяти, сделайте снимок в начале сеанса отладки.
Запустите сценарий, который вызвал срабатывание первой точки останова.
После приостановки отладчика на первой точке останова на сокращенной панели инструментов Использование памяти выберите команду Сделать снимок.
Нажмите клавишу F5, чтобы запустить приложение до второй точки останова.
Теперь создайте еще один моментальный снимок.
На этом этапе можно начать анализировать данные.
Анализ данных использования памяти
В строках сводной таблицы "Использование памяти" приводятся моментальные снимки, сделанные во время сеанса отладки, и ссылки на дополнительные подробные представления.
Если сделать несколько снимков, в каждой строке сводной таблицы будет отображаться разница значений с предыдущим снимком.
Чтобы выполнить анализ данных об использовании памяти, щелкните одну из ссылок, которая позволяет открыть подробный отчет об использовании памяти:
- Чтобы отобразить подробности об изменении текущего моментального снимка по сравнению с предыдущим, щелкните ссылку разницы слева от стрелки (). Красная стрелка обозначает, что объем используемой памяти увеличился, а зеленая — что он снизился.
Чтобы быстрее выявить проблемы с памятью, типы объектов в отчетах об изменениях можно отсортировать по наибольшему увеличению общего объема (щелкните ссылку "Изменения" в столбце Объекты (разн.) ) или по наибольшему увеличению размера кучи (щелкните ссылку "Изменения" в столбце Размер кучи (разн.) ).
Чтобы отобразить подробности только для выбранного моментального снимка, щелкните ссылку "Без изменений".
Отчет отображается в новом окне.
Отчеты об управляемых типах
Щелкните текущую ссылку в ячейке Объекты (разн.) или Выделения (разн.) в сводной таблице "Использование памяти".
В дереве Объекты, на которые указывает ссылка отображаются ссылки, активные для выбранного в верхней области типа.
Чтобы отобразить экземпляры типа, выбранного в верхней области, щелкните значок .
На панели Экземпляры , которая открывается в верхней области, отображаются экземпляры выбранного объекта текущего снимка. На панелях Пути к корню и Объекты, на которые указывает ссылка отображаются объекты, которые ссылаются на выбранный экземпляр, а также типы, на которые ссылается выбранный экземпляр. Если создать снимок после остановки отладчика и навести указатель мыши на ячейку в столбце Значение, во всплывающей подсказке отобразятся значения объекта.
Отчеты о собственных типах
Щелкните текущую ссылку в ячейке Выделения (разн.) или Размер кучи (разн.) в сводной таблице "Использование памяти", отображаемой в окне Средства диагностики.
В окне Экземпляры отображаются все экземпляры выбранного типа. При выборе экземпляра на панели Стек вызовов выделений отображается стек вызовов, использованный для создания этого экземпляра.
Чтобы отобразить стек вызовов для выбранного типа, в раскрывающемся меню Режим просмотра выберите пункт Представление стеков .
Отчеты об изменениях
В окне Средства диагностики щелкните в необходимой ячейке сводной таблицы Использование памяти разницу в значениях.
Выберите моментальный снимок в списке Сравнить с , в котором отображаются управляемые или собственные отчеты.
С помощью отчета об изменениях в основной отчет можно добавить столбцы, помеченные надписью (Разн.) , в которых будет отображаться разница между двумя выбранными снимками. Отчет об изменениях собственных типов может выглядеть следующим образом.
Блоги и видео
Следующие шаги
В этом руководстве вы узнали, как собирать и анализировать данные об использовании памяти. Если вы уже ознакомились с общими сведениями о профилировщике, можно перейти к анализу данных об использовании ЦП в приложениях.
В примере, описанном в этой статье, проблема заключается в том, что приложение не отвечает на запросы своевременно.
Открытие дампа памяти в Visual Studio
Откройте дамп памяти в Visual Studio с помощью команды меню Файл > Открыть > Файл и выберите дамп памяти.
Обратите внимание, что на странице сводки дампа памяти в разделе Действие отображается пункт Выполнить диагностический анализ.
Выберите это действие, чтобы запустить отладчик и открыть новую страницу Диагностический анализ со списком доступных параметров анализатора, упорядоченных по базовым симптомам.
Выбор и выполнение анализаторов для дампа
Для изучения этих симптомов в разделе Скорость реагирования процесса доступны варианты, которые наиболее точно соответствуют проблеме в нашем примере.
Анализатор представит результаты на основе сведений о процессе и данных CLR, полученных в дампе памяти.
Просмотр результатов анализатора
В нашем примере анализатор обнаружил две ошибки. Выберите результат анализатора, чтобы просмотреть сводку анализа и предлагаемое исправление.
В разделе Сводка анализа указано, что пул потоков CLR испытывает нехватку ресурсов. Согласно этой информации предполагается, что среда CLR использовала все доступные потоки пула потоков. Это означает, что служба не сможет отвечать на новые запросы, пока поток не будет освобожден.
Исправление в нашем примере — это рекомендация не выполнять синхронное ожидание мониторов, событий, задач или других объектов, которые могут блокировать поток, и проверить, можно ли обновить метод как асинхронный.
Переход к проблемному коду
Следующим заданием является поиск проблемного кода.
Когда вы щелкнете ссылку Показать стек вызовов, Visual Studio немедленно переключится на потоки, которые демонстрируют это поведение.
В окне Стек вызовов отобразятся методы, которые могут быстро отличить пользовательский код (SyncOverAsyncExmple. ) от кода платформы (System. ).
Каждый кадр стека вызовов соответствует методу. Когда вы дважды щелкнете кадр стека, Visual Studio перейдет к коду, выполнение которого приводит непосредственно к описанному сценарию в этом потоке.
В нашем примере нет символов или кода, но на странице Символы не загружены можно выбрать вариант Декомпилировать исходный код .
В декомпилированном источнике ниже асинхронная задача (ConsumeThreadPoolThread) вызывает синхронную блокирующую функцию.
Метод DoSomething() содержит метод WaitHandle.WaitOne, который блокирует текущий поток пула потоков до получения сигнала.
Чтобы ускорить реагирование приложений, важно снять блокировку синхронного кода из всех асинхронных контекстов.
Долгие годы С++ программисты, пишущие под Linux язвительно пеняли разработчикам на С++ под Windows отсутствием в Visual Studio нормального профилировщика памяти. Вот в Линуксе, дескать, есть Valgrind, который решает все проблемы, а в студии что: расставляй какие-то макросы, анализируй какие-то логи — мрак. Клевета! Хотя и правда. Вернее, это было правдой до выхода Visual Studio 2015, в которой наконец-то (ура 3 раза!) присутствует нормальный профилировщик памяти, позволяющий ловить утечки памяти с закрытыми глазами, одной левой и даже не просыпаясь!
В этой статье мы посмотрим, что он умеет и как им пользоваться.
Запускаем Visual Studio 2015, создаём новый консольный проект на С++ и пишем в него следующий код:
Теперь запускаем приложение под отладчиком (F5) и видим появившуюся в Visual Studio панель Diagnostic Tool (см. скриншот выше).
Она показывает загрузку процессора и памяти, но нам интересно не это. Самое ценное в этой панели — нижняя часть, позволяющая нам создавать снимки памяти приложения в любой момент времени. По-умолчанию эта функциональной отключена (поскольку затормаживает работу приложения), чтобы её включить нужно нажать кнопку «Enable snapshots» и перезапустить приложение под отладчиком.
Теперь нам становится доступной кнопка «Take Snapshot», давайте её нажмём.
И у нас появился первый снимок памяти! Мы можем кликнуть по нему дважды и посмотреть, что там внутри:
Мы видим список всех выделений памяти, которые произошли в нашем процессе, типы созданных переменных, их количество и размер в байтах. Приложение наше простое, как двери, но всё же… Что это за массив char[] размером в 100 байт? Как узнать, где он создаётся? Просто кликаем по нему дважды — попадаем в список экземпляров объектов этого типа. У нас он всего один. Внизу окна мы видим стек вызовов, по ходу выполнения которого был аллоцирован данный блок памяти. Смотрим кто на вершине этого стека:
Итак, это функция main(), строка №9. Двойной клик переведёт нас прямо к коду.
О боже, как же так! Оказывается, я только собирался написать тот простой код, который привёл сверху, а по ходу дела создал в цикле массив на 100 байт, который нигде не удаляется и приводит к утечке памяти. Даже и не знаю как бы я её нашел, если бы не новый профилировщик Visual Studio!
«Ладно, хватит прикалываться» — скажет практично настроенный читатель — «Нашел он выделение одного массива в программе из 7 строк, где никакой другой памяти не выделяется. У меня вот в проекте 150 тыщ классов и кода как текста в „Войне и мире“, ты попробуй тут найди где там что утекает!».
А давайте попробуем. Для реализма создадим новый MFC-проект, который тянет за собой (сюрприз!) — MFC. Проект создаём стандартным визардом, ничего не меняя. И вот у нас пустой проект из 55 файлов — да здравствует «минималистичность» MFC. Хорошо хоть билдится.
Найдём метод CMFCApplication1App::OnAppAbout() и допишем в него уже знакомую нам утечку памяти:
Теперь запустим это приложение под профилировщиком памяти. Как вы догадываетесь, уже по ходу запуска MFC навыделяет себе памяти. Сразу после запуска создадим первый снимок памяти, а дальше нажмём 10 раз кнопку «About». Каждый раз будет показан модальный диалог (что приведёт к некоторому количеству операций выделения и освобождения памяти) и, как вы догадались, каждый раз будет происходить утечка 100 байт памяти. В конце создадим ещё один снимок памяти — теперь у нас их два.
Первое, что мы видим, это разницу в количестве выделенной памяти — во втором снимке на 58 выделений больше, что в сумме составляет 15.71 КB. В основном это память выделенная MFC для своих внутренних нужд (прямо как в вашем проекте со 150 тысячами классов, да?), которая потом, наверное, будет MFC освобождена. Но нас интересует не она, а утечки памяти в нашем коде. Давайте откроем второй снимок памяти:
В принципе, уже отсюда можно делать кое-какие выводы: у нас есть 10 указателей на char, по 100 байт каждый — вполне вероятно что 10 выделений памяти связаны с 10-ю кликами по кнопке, а значит можно искать в коде число «100» ну или перейти по стеку вызовов в место выделения памяти для этого массива. Но ладно, усложним себе задачу — представим, что у нас здесь не 7 строк с указателями на выделенную память, а 700 или 7000. Среди них могут быть и блоки большего размера, и другие блоки, существующие в количестве 10 экземпляров. Как же нам отследить только то, что было создано между двумя снимками памяти? Элементарно — для этого есть комбик «Compare to» в верхней части окна. Просто выбираем там снимок №1 и видим только разницу между моментом перед первым кликом по кнопке и моментом после 10-го клика. Теперь табличка выглядит значительно чище, тут уже и слепой заметит, на что следует обратить внимание.
Плюс у нас есть сортировка по столбцам и поиск по типам данных.
В общем, инструмент у Microsoft получился очень хороший, прямо редкий случай, когда и всё необходимое на месте, и ничего лишнего нет.
Я использовал режим отладки в Visual Studio раньше, но мне никогда не приходилось использовать окно памяти. Если бы у меня было простое приложение, которое вычисляет a = b + c и делало b = 8 и c = -2, как я могу найти адреса a, b и c в окне памяти и их значения без использования часов?
Если бы я хотел сделать то же самое, но в среде Linux, как я мог бы добиться этого?
Решение
Но чтобы увидеть все переменные в окне памяти, они должны быть в пределах нескольких байтов друг от друга, так как оно показывает непрерывную память. Для локальных переменных в стеке это обычно не будет проблемой. Для целочисленных переменных вы можете легче просматривать их в удобочитаемом формате, щелкнув правой кнопкой мыши в окне памяти и изменив макет (например, выберите 4-байтовые целые числа со знаком со знаком).
Сказав все это, кажется, что было бы намного проще использовать окно наблюдения, поскольку все уже хорошо помечено, и легко определить, какое значение связано с какой переменной.
Другие решения
Скопируйте / вставьте следующий код и отладьте его:
Запустите отладчик Visual Studio, добавьте наблюдение ко всем переменным (щелкните правой кнопкой мыши каждую переменную и нажмите «Добавить наблюдение»). Теперь, если он не открыт, откройте окно просмотра (меню отлаживать → Окно → * Watch) и перетащите переменную adresse_int_variable из окна просмотра в окно памяти. Вы получите следующее:
Вы заметите, что значение 41 появляется по этому адресу. В шестнадцатеричном, 0x41 равен 65. Таким образом, вы видите, что адрес переменной int_variable фактически содержит 65. (Обратите внимание, что в действительности память содержит биты: 01000001, но она представлена в шестнадцатеричном формате для удобства чтения.)
Войти &int_variable2 в окне памяти вы получите:
int_variable2 держит значение 10000 и в шестнадцатеричном виде это 0x2710 , Теперь ищите значения, хранящиеся для переменных char_variable_1 а также char_variable_2 : ты видишь 0x41 а также 0x42 , Это способ A а также B закодированы в ASCII таблица . Обратите внимание, что в памяти int_variable а также char_variable_1 подобные.
Наконец введите &mystruct в окне памяти, и вы увидите:
Это соответствует памяти mystruct переменная, которая содержит четыре переменные ( int и три char с). Вы видите age переменная ( 10000 = 0x2710 ) и три следующих символа: A , B а также 65 которые хранятся как 0x41 , 0x42 , 0x41 (справа налево). Обратите внимание, что в правой части окна вы можете увидеть ABA в виде строкового представления памяти (если нет, щелкните правой кнопкой мыши окно и выберите ANSI).
Попробуйте с более сложными переменными, читайте о порядок байт а также выравнивание структуры данных . Смотрите также страница окна памяти на MSDN .
Читайте также: