使用Visual Studio Compiler分析内联C++函数

mil*_*anw 15 c++ profiling inline visual-studio visual-studio-debugging

当大量代码被编译器内联时,我怎样才能理解Windows上的C++分析数据?即我当然想要测量实际运行的代码,因此根据定义,我将测量代码的优化构建.但似乎我尝试实际设法解决内联函数的工具都没有.

我已经尝试过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;
}
Run Code Online (Sandbox Code Playgroud)

在发布模式下使用Visual Studio编译它.或者在命令行上,尝试cl /O2 /Zi /Zo /EHsc main.cpp.然后尝试使用Visual Studio中的CPU Sampling Profiler对其进行配置.你最多会看到这样的事情:

由于缺少内联框架,导致配置文件混乱

VTune 2018在Windows上看起来很相似.在Linux上,perf和VTune显示来自内联函数的帧没有问题...这个功能,在我看来对C++工具至关重要,真的不是非Premium/Ultimate Visual Studio工具链的一部分吗?Windows上的人们如何处理这个问题?那有什么意义/Zo呢?

编辑:我只是尝试用clang编译上面的最小例子,它产生不同的,但仍然不满意的结果?我编译了clang 6.0.0(trunk),从LLVM rev 318844和clang rev 318874构建.然后我编译我的代码clang++ -std=c++17 -O2 -g main.cpp -o main.exe并再次使用Visual Studio中的Sampling Profiler运行生成的可执行文件,结果是:

在使用clang编译后,内联框架显示在配置文件中

所以现在我看到了这个burn功能,但丢失了源文件信息.此外,uniform_real_distribution仍然没有在任何地方显示.

编辑2:正如评论中所建议的那样,我现在也尝试了clang-cl与上述相同的论点cl,即:clang-cl.exe /O2 /Zi /Zo /EHsc main.cpp.这产生了相同的结果clang.exe,但我们也得到了一些有效的源映射:

clang-cl显示内联和一些功能源映射

编辑3:我原本以为clang会神奇地解决这个问题.遗憾的是,它没有.大多数内联框架仍然缺失:(

编辑4: VTune中不支持嵌入式框架,用于使用MSVC/PDB版本构建的应用程序:https://software.intel.com/en-us/forums/intel-vtune-amplifier-xe/topic/749363

Had*_*ais 3

我尝试过 Visual Studio 2017 Professional 和 VTune 2018 中的采样分析器。我尝试启用 /Zo,但它似乎没有任何影响。

我发现以下资源似乎表明只有 Visual Studio Ultimate 或 Premium 支持内联框架信息 - 对于 Visual Studio 2017 来说这仍然如此吗?

幸运的是,我已经安装了三个不同版本的VS。我可以告诉您有关内联函数信息功能支持的更多信息,如您引用的文章中所述:

  • 即使我指定 /d2Zi+,VS Community 2013 Update 5 也不支持显示内联函数。似乎只有 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 支持它。

VC++ 博客上没有关于 VS 2017 采样分析器的任何改进的公告,因此我认为它与 VS Community 2015 的分析器相比没有任何改进。

请注意,不同版本的编译器可能会做出不同的优化决策。例如,我观察到 VS 2013 和 2015 没有内联该burn函数。

通过使用 VS Community 2015 Update 3,我得到的分析结果与第三张图片中显示的结果非常相似,并且突出显示了相同的代码。

现在,我将讨论这些附加信息在解释分析结果时如何有用,如何通过更多努力手动获取该结果,以及如何解释结果(尽管有内联函数)。

当编译器内联大量代码时,如何理解 Windows 上的 C++ 分析数据?

VS 分析器只会将成本归因于未内联的函数。对于内联的函数,成本将被累加并包含在某些未内联的调用者函数(在本例中为函数burn)中。

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;
Run Code Online (Sandbox Code Playgroud)

如果没有该功能,您将必须手动反汇编发出的可执行二进制文件(使用 VS 调试器或使用dumpbin)并找到所有 x86call指令。通过将其与源代码中调用的函数进行比较,您可以确定哪些函数被内联。

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;
Run Code Online (Sandbox Code Playgroud)

考虑到所有这些,我在这里看到的唯一潜在的优化机会是记住log2.

VTune 采样分析器肯定比 VS 采样分析器更强大。特别是,VTune 将成本归因于各个源代码行或汇编指令。然而,这种归因是高度近似的,而且常常是荒谬的。因此,在解释以这种方式可视化的结果时,我会非常小心。我不确定 VTune 是否支持增强优化调试信息,或者在多大程度上支持将成本归因于内联函数。提出这些问题的最佳地点是英特尔 VTune Amplifier 社区论坛