В этой статье мы посмотрим, что он умеет и как им пользоваться.
Запускаем Visual Studio 2015, создаём новый консольный проект на С++ и пишем в него следующий код:
#include<iostream>
int main()
{
for (;;)
{
std::cout << "Hello, Habr!";
getchar();
}
return 0;
}
Теперь запускаем приложение под отладчиком (F5) и видим появившуюся в Visual Studio панель Diagnostic Tool (см. скриншот выше).
Она показывает загрузку процессора и памяти, но нам интересно не это. Самое ценное в этой панели — нижняя часть, позволяющая нам создавать снимки памяти приложения в любой момент времени. По-умолчанию эта функциональной отключена (поскольку затормаживает работу приложения), чтобы её включить нужно нажать кнопку «Enable snapshots» и перезапустить приложение под отладчиком.
Теперь нам становится доступной кнопка «Take Snapshot», давайте её нажмём.
И у нас появился первый снимок памяти! Мы можем кликнуть по нему дважды и посмотреть, что там внутри:
Мы видим список всех выделений памяти, которые произошли в нашем процессе, типы созданных переменных, их количество и размер в байтах. Приложение наше простое, как двери, но всё же… Что это за массив char[] размером в 100 байт? Как узнать, где он создаётся? Просто кликаем по нему дважды — попадаем в список экземпляров объектов этого типа. У нас он всего один. Внизу окна мы видим стек вызовов, по ходу выполнения которого был аллоцирован данный блок памяти. Смотрим кто на вершине этого стека:
Итак, это функция main(), строка №9. Двойной клик переведёт нас прямо к коду.
О боже, как же так! Оказывается, я только собирался написать тот простой код, который привёл сверху, а по ходу дела создал в цикле массив на 100 байт, который нигде не удаляется и приводит к утечке памяти. Даже и не знаю как бы я её нашел, если бы не новый профилировщик Visual Studio!
«Ладно, хватит прикалываться» — скажет практично настроенный читатель — «Нашел он выделение одного массива в программе из 7 строк, где никакой другой памяти не выделяется. У меня вот в проекте 150 тыщ классов и кода как текста в „Войне и мире“, ты попробуй тут найди где там что утекает!».
А давайте попробуем. Для реализма создадим новый MFC-проект, который тянет за собой (сюрприз!) — MFC. Проект создаём стандартным визардом, ничего не меняя. И вот у нас пустой проект из 55 файлов — да здравствует «минималистичность» MFC. Хорошо хоть билдится.
Найдём метод CMFCApplication1App::OnAppAbout() и допишем в него уже знакомую нам утечку памяти:
CMFCApplication1App::OnAppAbout()
{
CAboutDlg aboutDlg;
aboutDlg.DoModal();
char* l = new char[100];
}
Теперь запустим это приложение под профилировщиком памяти. Как вы догадываетесь, уже по ходу запуска 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 получился очень хороший, прямо редкий случай, когда и всё необходимое на месте, и ничего лишнего нет.
Комментарии (13)
khim
27.08.2015 15:55+3
sergestus
27.08.2015 16:26Очень полезная вещь! А почему в случае визуального сравнения двух снимков мы видим 58 выделений и 15.71 КВ, а в случае исползования «Compare to» — 10 выделений и 1 КВ?
tangro
27.08.2015 17:04Оно показывает в списке те типы, которые смогло распознать, если память выделялась не через типизированный new, а malloc-ом, то это будет показано под пунктом «Undeterminated type», он по-умолчанию отключен, но его можно включить — справа сверху кнопка фильтра.
outcoldman
27.08.2015 17:59Что-то я так и не понял, откуда взялась та строка, если вы ее не писали? :D
tangro
27.08.2015 19:40+6Ну так а кто же в коде специально утечки памяти пишет? Они сами появляются! :D
maaGames
27.08.2015 18:58+2Попробовал 2015. Обнаружил один шикарнейший момент: когда в отладчике шагаешь по F10, то рядом с каждой строкой выводится время, затраченное на исполнения этой строки. В отладке это крайне редко нужно, но мне понравилось.
Правда, пришлось пересобирать boost и прочие библиотеки, но в целом всё завелось почти сразу. Осталось поставить свеженькую помидорку, потому что без неё даже 2015 студией пользоваться невозможно.
beduin01
27.08.2015 23:10Вопрос: D профайлить можно? Про плагин интеграции знаю, но интересует именно профайлер кода.
Door
Я смотрел какое-то видео, увидел это окошко и именно из-за него захотел Visual Studio 2015! Ещё не пользовался, но — УРА, круто, наконец-то. Дождались!