Pull to refresh
0
Инфопульс Украина
Creating Value, Delivering Excellence

Новый профилировщик памяти в Visual Studio 2015

Reading time 3 min
Views 29K
Долгие годы С++ программисты, пишущие под Linux язвительно пеняли разработчикам на С++ под Windows отсутствием в Visual Studio нормального профилировщика памяти. Вот в Линуксе, дескать, есть Valgrind, который решает все проблемы, а в студии что: расставляй какие-то макросы, анализируй какие-то логи — мрак. Клевета! Хотя и правда. Вернее, это было правдой до выхода Visual Studio 2015, в которой наконец-то (ура 3 раза!) присутствует нормальный профилировщик памяти, позволяющий ловить утечки памяти с закрытыми глазами, одной левой и даже не просыпаясь!

В этой статье мы посмотрим, что он умеет и как им пользоваться.



Запускаем 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 получился очень хороший, прямо редкий случай, когда и всё необходимое на месте, и ничего лишнего нет.
Tags:
Hubs:
+37
Comments 13
Comments Comments 13

Articles

Information

Website
www.infopulse.com
Registered
Founded
1992
Employees
1,001–5,000 employees
Location
Украина