Skip to content

Latest commit

 

History

History
148 lines (102 loc) · 9.46 KB

clq3ukcu2000f08jvfjtr3we8.md

File metadata and controls

148 lines (102 loc) · 9.46 KB
title seoTitle seoDescription datePublished cuid slug cover tags
Профилирование C++ кода в Visual Studio без инструментирования
Профилирование C++ кода в Visual Studio без инструментирования
Разбираемся, как решать проблемы производительности программы с помощью профилировщика Visual Studio без инструментирования
Wed Dec 13 2023 14:08:43 GMT+0000 (Coordinated Universal Time)
clq3ukcu2000f08jvfjtr3we8
profilirovanie-c-koda-v-visual-studio-bez-instrumentirovaniya
optimization, cpp, visual-studio, profiling, optimizaciya-koda, profilirovanie, profilirovanie-c

Исходные данные

Рассмотрим код с проблемой производительности - он выполняется слишком долго. Проблема может показаться очевидной. Да что там показаться, так оно и есть. Но представим, что мы не знаем, где проблема.

#include <iostream>
#include <fstream>

using namespace std;

void WriteFile1(int n)
{
  for (int i = 0; i < n; ++i)
  {
    fstream file("file.txt");
    if (file)
      file << i << " ";
  }
}

void WriteFile2(int n)
{
  for (int i = 0; i < n; ++i)
  {
    fstream file("file.txt");
    if (file)
      file << i << " ";
  }
}

int main()
{
  cout << "WriteFile small" << endl;
  WriteFile1(100000);
  cout << "WriteFile big" << endl;
  WriteFile2(1000000);
  return 0;
}

Можно попытаться решить задачу без специальных инструментов, измерив время выполнения участков кода "вручную". Для этого фиксируется время начала и конца выполнения участка кода и разница между ними выводится на экран.

Но мы не хотим писать лишний код, поэтому используем профилировщик Visual Studio. Профилирование в Visual Studio можно выполнять с инструментированием и без. Под инструментированием подразумевается встраивание в исходный код специальных меток во время компиляции. По этим меткам профилировщик будет делать измерения. Главный недостаток здесь - нужна специальная сборка. Плюс программа становится громоздкой и немного "тормознутой".

Для профилирования без инструментирования достаточно отладочной сборки программы.Такая сборка, в общем-то, всегда под рукой и будет пошустрее инструментированной. Да, профилирование без инструментированиея выдает меньше данных и они могут быть не очень точные. Но практика показывает, что и этого часто достаточно.

Первый запуск профилировщика

Visual Studio позволяет профилировать приложения даже если они не разрабатываются в этой среде. Пусть это будет наш случай.

Запускаем Visual Studio и пропускаем экран приветствия.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702446946637/a5ba8807-7826-4be5-b349-da7240ca062b.png align="center")

Переходим к запуску профилировщика производительности.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702458065693/26271c5c-c441-4eaf-846d-4aa1e622dbcb.png align="center")

В качестве целевого объекта выбираем "Исполняемый файл".

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702458240762/c6c87ba7-b02a-4d61-a4f3-0e2afc88e794.png align="center")

Настраиваем параметры запуска исполняемого файла. Как минимум указываем путь к файлу. Рабочая директория пусть совпадает с директорией исполняемого файла.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702458772541/d072aefd-0c81-4621-a4ec-8a6ffc43502e.png align="center")

Теперь остается выбрать режим профилирования и можно запускать. "Использование ЦП" - наш случай. Профилирование без инструментирования.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702458927006/635af341-ec66-4657-90d7-05a82e0b6900.png align="center")

После нажатия на "Начать" будет запущена целевая программа под профилировщиком. По окончании работы программы профилировщик построит отчет.

В отчете приводится разная информация, но нам интересен критический путь. Критический путь отражает самый ресурсозатратный стек вызовов в программе. То есть в каких функциях программа проработала дольше всего. Критических путей может быть несколько. Это возможно, когда критические пути похожи по характеристикам друг с другом, либо в значительной степени выделяются на фоне остального стека.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702469762582/fb8b7759-d0ff-4078-845c-4bb1f5cc5245.png align="center")

Таблица критического пути содержит два параметра:

  • Общее время ЦП - это время, затраченное функцией вместе с ее внутренними вызовами других функций. (ЦП - центральный процессор).

  • Собственное время ЦП - это время, затраченное функцией, исключая ее внутренние вызовы других функций.

При клике на любой элемент стека критического пути открывается представление функций. Посмотрим данные по функции WriteFile2().

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702470615312/1743b601-daea-4e94-bcc9-b0588fff051f.png align="center")

На представлении функций становится понятно, что проблема производительности связана со строкой открытия файла. Она занимает 84,48% всего времени выполнения программы. Предположим, что это связано с тем, что открытие файла выполняется на каждой итерации цикла. Слишком часто...

Второй запуск профилировщика

Попробуем вынести код открытия файла за пределы цикла и снова запустим профилирование, чтобы убедиться в правильности решения.

#include <iostream>
#include <fstream>

using namespace std;

void WriteFile1(int n)
{
  fstream file("file.txt");
  if (file)
    for (int i = 0; i < n; ++i)
      file << i << " ";
}

void WriteFile2(int n)
{
  fstream file("file.txt");
  if (file)
    for (int i = 0; i < n; ++i)
      file << i << " ";
}

int main()
{
  cout << "WriteFile small" << endl;
  WriteFile1(100000);
  cout << "WriteFile big" << endl;
  WriteFile2(1000000);
  return 0;
}

Повторное профилирование показало... Ничего не показало. Программа выполнилась настолько быстро, что профилировщик не зафиксировал время выполнения вложенных функций. Общее время ЦП стало равным 3 против 6159 перед оптимизацией. Задача решена.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1702471125466/f3873461-27d2-4e11-adf4-d439eaba8d2c.png align="center")

Вывод

Если есть необходимость в точных измерениях времени выполнения функций, а также подсчета количества их вызовов, то без способа с инструментированием не обойтись. В остальных случаях подойдет рассмотренный метод.


Телеграм: Так себе программист