Профилирование встроенных функций С++ с помощью компилятора Visual Studio

Как я могу осмыслить данные профилирования С++ в Windows, когда компилятор получает встроенный код? То есть Я, конечно, хочу измерить код, который действительно запускается, поэтому по определению я собираюсь измерить оптимизированную сборку кода. Но похоже, что ни один из инструментов, которые я пытаюсь, фактически не может решить встроенные функции.

Я пробовал как профилировщик выборки в Visual Studio 2017 Professional, так и VTune 2018. Я попытался включить /Zo, но, похоже, это не влияет.

Я нашел следующий ресурс, который, по-видимому, указывает, что только Visual Studio Ultimate или Premium поддерживают встроенную фрейм-информацию - это все еще верно для Visual Studio 2017? https://social.msdn.microsoft.com/Forums/en-US/9df15363-5aae-4f0b-a5ad-dd9939917d4c/which-functions-arent-pgo-optimized-using-profile-data?forum=vsdebug

Вот пример кода:

#include <cmath>
#include <random>
#include <iostream>

inline double burn()
{
    std::uniform_real_distribution<double> uniform(-1E5, 1E5);
    std::default_random_engine engine;
    double s = 0;
    for (int i = 0; i < 100000000; ++i) {
        s += uniform(engine);
    }
    return s;
}

int main()
{
    std::cout << "random sum: " << burn() << '\n';
    return 0;
}

Скомпилируйте его с помощью Visual Studio в режиме Release. Или в командной строке попробуйте cl /O2 /Zi /Zo /EHsc main.cpp. Затем попробуйте профилировать его с помощью Profiler Profiler в Visual Studio. Вы больше всего увидите что-то вроде этого:

запутанный профиль, поскольку встроенные кадры отсутствуют

VTune 2018 похож на Windows. На Linux, perf и VTune нет проблем с отображением кадров из встроенных функций... Является ли эта функция, которая, на мой взгляд, решающей для инструментария на С++, действительно не является частью инструментальных цепей без Premium/Ultimate Visual Studio? Как люди с Windows справляются с этим? Какая точка /Zo тогда?

EDIT: Я просто попытался скомпилировать минимальный пример выше с clang, и он создает разные, но все же неудовлетворительные результаты? Я скомпилировал clang 6.0.0 (trunk), построил из LLVM rev 318844 и clang rev 318874. Затем я скомпилировал свой код с clang++ -std=c++17 -O2 -g main.cpp -o main.exe и снова запустил результирующий исполняемый файл с помощью Sampling Profiler в Visual Studio, результат:

встроенные кадры отображаются в профиле после компиляции с помощью clang

Итак, теперь я вижу функцию burn, но потерял информацию о исходном файле. Кроме того, uniform_real_distribution по-прежнему не отображается нигде.

EDIT 2: Как было предложено в комментариях, я также опробовал clang-cl с теми же аргументами, что и cl выше, т.е.: clang-cl.exe /O2 /Zi /Zo /EHsc main.cpp. Это дает те же результаты, что и clang.exe, но мы также получаем несколько рабочих сопоставлений источника:

clang-cl показывает inliners и несколько функциональное сопоставление источников

РЕДАКТИРОВАТЬ 3: Я изначально думал, что Кланг магически решает эту проблему. Это не печально. Большинство встроенных кадров по-прежнему отсутствуют: (

РЕДАКТИРОВАТЬ 4: Встроенные фреймы не поддерживаются в VTune для сборки прикладных программ с сборками MSVC/PDB: https://software.intel.com/en-us/forums/intel-vtune-amplifier-xe/topic/749363

Ответы

Ответ 1

Я пробовал как прокси-сервер выборки в Visual Studio 2017 Professional, а также VTune 2018. Я попытался включить /Zo, но это похоже, не влияет.

Я нашел следующий ресурс, который, по-видимому, указывает, что только Visual Studio Ultimate или Premium поддерживают встроенную информацию о кадре - это все еще верно для Visual Studio 2017?

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

  • VS Community 2013 Update 5 не поддерживает показ встроенных функций, даже когда я указываю /d 2Zi+. Похоже, что он поддерживается только в VS 2013 Premium или Ultimate.
  • VS Community 2015 Update 3 поддерживает показ встроенных функций (функция, обсуждаемая в статье). По умолчанию указывается /Zi. /Zo активируется неявно с /Zi, поэтому вам не нужно явно указывать его. Поэтому вам не нужны VS 2015 Premium или Ultimate.
  • VS Community 2017 с последним обновлением не поддерживает показ встроенных функций независимо от /Zi и/Zo. Кажется, что он поддерживается только в VS 2017 Professional и/или Enterprise.

В блоге VС++ не сообщается о каких-либо улучшениях в профилировщике выборки VS 2017, поэтому я не думаю, что это лучше по сравнению с профилировщиком VS Community 2015.

Обратите внимание, что разные версии компилятора могут принимать различные решения по оптимизации. Например, я заметил, что VS 2013 и 2015 не поддерживают функцию burn.

Используя VS Community 2015 Update 3, я получаю профилирующие результаты, очень похожие на то, что показано в третьем рисунке, и тот же код выделены.

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

Как я могу осмыслить данные профилирования С++ в Windows, когда много код встраивается компилятором?

Профилировщик VS будет относить затраты только к функциям, которые не были включены. Для функций, которые были встроены, затраты будут добавлены и включены в некоторую функцию вызывающего абонента, которая не была включена (в этом случае функция burn).

Сложив оценочное время выполнения не-inline-вызванных функций из burn (как показано на рисунке), получаем 31.3 + 22.7 + 4.7 + 1.1 = 59.8%. Кроме того, расчетное время выполнения Function Body, как показано на рисунке, составляет 40,2%. Обратите внимание, что 59,8% + 40,2% = 100% времени, проведенного в burn, как и должно быть. Другими словами, 40,2% времени, проведенного в burn, было потрачено в теле функции и на любые функции, которые были в ней встроены.

40,2% - это много. Следующий логический вопрос: какие функции встраиваются в burn? Используя эту функцию, которую я обсуждал ранее (которая доступна в VS Community 2015), я могу определить, что в burn были установлены следующие функции:

std::mersenne_twister_engine<unsigned int,32,624,397,31,2567483615,11,4294967295,7,2636928640,15,4022730752,18,1812433253>::{ctor};
std::mersenne_twister<unsigned int,32,624,397,31,2567483615,11,7,2636928640,15,4022730752,18>::{ctor};
std::mersenne_twister<unsigned int,32,624,397,31,2567483615,11,7,2636928640,15,4022730752,18>::seed;
std::uniform_real<double>::operator();
std::uniform_real<double>::_Eval;
std::generate_canonical;

Без этой функции вам придется вручную разобрать испущенный исполняемый двоичный файл (либо с помощью отладчика VS, либо с помощью dumpbin) и найдите все инструкции x86 call. Сравнивая это с функциями, вызванными в исходном коде, вы можете определить, какие функции были вложены.

Возможности профилирования выборки VS до и вплоть до конца VS 2017 завершаются на этом этапе. Но это действительно не значительное ограничение. Как правило, не так много функций встраиваются в одну и ту же функцию из-за жесткого верхнего предела, налагаемого компилятором на размер каждой функции. Таким образом, в целом возможно вручную проверить исходный код и/или код сборки каждой встроенной функции и посмотреть, будет ли этот код значительно влиять на время выполнения. Я сделал это и, скорее всего, тот случай, когда тело burn (исключая встроенные функции) и эти две встроенные функции в основном отвечают за 40,2%.

std::mersenne_twister<unsigned int,32,624,397,31,2567483615,11,7,2636928640,15,4022730752,18>::seed;
std::uniform_real<double>::_Eval;

Учитывая все это, единственная потенциальная возможность оптимизации, которую я вижу здесь, состоит в том, чтобы memoize результаты log2.

Профилировщик выборки VTune, безусловно, более мощный, чем профилировщик выборки VS. В частности, атрибуты VTune зависят от отдельных строк исходного кода или инструкций по сборке. Однако эта атрибуция очень аппроксимируется и часто бессмысленна. Поэтому я был бы очень осторожен при интерпретации результатов, визуализированных таким образом. Я не уверен, поддерживает ли VTune "Улучшить оптимизированную отладочную информацию" или в какой степени он поддерживает расходы на привязку к встроенным функциям. Лучшее место, чтобы задать эти вопросы, - это форум сообщества Intel VTune Amplifier.

Ответ 2

Я не уверен, правильно ли понял проблему, описанную в вашем вопросе. На вашем сайте я бы попробовал вариант компилятора /Ob0 Visual С++. Он должен отключить встроенное расширение.

Опция компилятора /Ob управляет встроенным расширением функций. За ним следует число 0, 1 или 2.

0 Отключает встроенные расширения. По умолчанию расширение происходит по усмотрению компилятора по всем функциям, часто называемым автоинкрементами.

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

2 Значение по умолчанию. Позволяет расширять функции, помеченные как встроенные, __inline или __forceinline, и любую другую функцию, которую выбирает компилятор.

/Ob2 действует, когда /O1, /O2 (Минимизировать размер, Максимизировать скорость) или /Ox (Включить большинство оптимизаций скорости).

Эта опция требует, чтобы вы включили оптимизацию с помощью /O1, /O2, /Ox или /Og.

Чтобы установить этот параметр компилятора в среду разработки Visual Studio

  • Откройте диалоговое окно "Свойства страницы проекта". Дополнительные сведения см. В разделе Работа с объектами проекта.
  • Разверните параметры конфигурации, C/С++ и выберите Оптимизация.
  • Измените свойство расширения встроенной функции.

введите описание изображения здесь

Для получения дополнительной информации прочитайте статью /Ob (расширение встроенной функции)