【问题标题】:Profiling inlined C++ functions with Visual Studio Compiler使用 Visual Studio 编译器分析内联 C++ 函数
【发布时间】:2018-05-12 12:02:38
【问题描述】:

当大量代码被编译器内联时,如何理解 Windows 上的 C++ 分析数据? IE。我当然想测量实际运行的代码,所以根据定义,我将测量代码的优化构建。但似乎我尝试的任何工具都无法真正解决内联函数。

我在 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 编译它。或者在命令行上尝试cl /O2 /Zi /Zo /EHsc main.cpp。然后尝试使用 Visual Studio 中的 CPU Sampling Profiler 对其进行分析。你最多会看到这样的东西:

VTune 2018 在 Windows 上看起来很相似。在 Linux 上,perf 和 VTune 可以毫无问题地显示内联函数的帧……这个在我看来对 C++ 工具至关重要的功能真的不属于非高级/终极 Visual Studio 工具链的一部分吗? Windows 上的人如何处理这个问题?那/Zo有什么意义呢?

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

所以现在我看到了burn 函数,但是丢失了源文件信息。此外,uniform_real_distribution 仍然没有显示在任何地方。

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

编辑 3: 我原本以为 clang 会神奇地解决这个问题。可悲的是,它没有。大多数内联帧仍然丢失:(

编辑 4: VTune 不支持使用 MSVC/PDB 构建的应用程序构建的内联框架:https://software.intel.com/en-us/forums/intel-vtune-amplifier-xe/topic/749363

【问题讨论】:

  • 基于此文档:visualstudio.com/vs/compare,当前的 VS2017 版本支持性能和诊断中心功能,现在没有 VS2017 Premium/Ultimate。如果您真的想知道结果,我可以设置不同的 VS 环境并稍后对其进行测试,但很抱歉,我不太确定您如何分析您的应用程序并解决此问题,例如您身边的第二个屏幕截图。如果我们想分析您的应用程序,据我所知,有两种方法,使用 VS IDE 或分析命令行。你的意思是你使用其他工具或命令行?
  • @JackZhai-MSFT 是的,CPU 分析器在 VS 2017 中可用。似乎不可用的是显示内联帧的功能。另请参阅:blogs.msdn.microsoft.com/vcblog/2013/06/27/… 这在 Visual Studio 2017 中根本不起作用
  • 您可以尝试使用clang-cl(或clang.exe --driver-mode=cl)并将您传递给cl的相同参数传递给cl,看看会发生什么?如果它仍然有效,那么我们就会知道我们不会错过一些可以解决此问题的神奇 cl 选项

标签: c++ visual-studio profiling inline visual-studio-debugging


【解决方案1】:

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

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

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

  • VS Community 2013 Update 5 不支持显示内联函数,即使我指定 /d2Zi+。它似乎只在 VS 2013 Premium 或 Ultimate 中受支持。
  • VS Community 2015 Update 3 确实支持显示内联函数(article 中讨论的功能)。默认情况下,指定 /Zi。 /Zo is enabled implicitly with /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,我得到的分析结果与 third picture 中显示的结果非常相似,并且突出显示了相同的代码。

现在我将讨论这些附加信息在解释分析结果时如何有用,如何通过更多的努力手动获得该信息,以及如何在使用内联函数的情况下解释结果。

如何理解 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 上,应该是这样。换句话说,burn 中花费的时间中有 40.2% 花费在函数体和其中内联的任何函数上。

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 2017(包括 VS 2017)的 VS 采样分析器的功能到此结束。但这真的不是一个重要的限制。通常,由于编译器对每个函数的大小施加了硬上限,因此不会有多少函数被内联到同一个函数中。因此,通常可以手动检查每个内联函数的源代码和/或汇编代码,并查看该代码是否会显着增加执行时间。我这样做了,很可能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;

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

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

【讨论】:

  • 非常感谢您的回复。我将把这个问题留一段时间,然后接受你的回答。其他一些 cmets:当内联信息实际存在时,我可以报告 VTune 的功能请求,要求他们也在那里显示内联帧。好的。但是,您是否为“该功能”引用了错误的链接?您链接到我之前也找到的博客文章两次,这并没有告诉我您实际上是如何找到内联函数的。或者你真的喜欢 Ultimate/Premium VS?请注意,我有 VS 2017 Professional,但看不到内联框架!
  • 所以你是说你的 VS Community 2015 实际上在分析器中显示了内联框架,跟随博客文章? 2015 年的免费版怎么会比 2017 年的商业版功能更多?
  • @milianw 是的,就是这样。我也很惊讶。
  • 刚刚检查了 VS Community 2017,我还看到了那里可用的功能。我将不得不用 Enterprise VS 版本重新检查我的另一台机器,看看该功能是否真的在那里被禁用(这将是非常令人惊讶的 - 希望我只是多次忽略它......)。在使用 Visual Studio 中的功能后,我不得不说它距离 Linux 上可用的工具还有很长的路要走……Perf/Hotspot/VTune 实际上可以对内联帧进行适当的成本归因,自动进行手动分析为你。伤心。我现在会接受你的回答。
  • 哈哈这真是一团糟!我使用的是 15.5.0 版本,但 visualstudio.com/en-us/news/releasenotes/vs2017-relnotes 没有提及这方面的任何内容......我认为这里有一个错误 - 为什么你在一个版本中看到它而不是另一个版本?为什么我在 Enterprise 2017 上看不到它?很明显,这里出了点问题……
【解决方案2】:

我不确定我是否正确理解了您的问题中描述的问题。在您的网站上,我会尝试 /Ob0 Visual C++ 编译器选项。它必须禁用内联扩展。

/Ob 编译器选项控制函数的内联扩展。后面必须跟数字 012

0 禁用内联扩展。默认情况下,编译器会自行决定对所有函数进行扩展,通常称为自动内联。

1 只允许扩展标记为 inline、__inline 或 __forceinline 的函数,或在类声明中定义的 C++ 成员函数中。

2 默认值。允许扩展标记为 inline、__inline 或 __forceinline 的函数以及编译器选择的任何其他函数。

/Ob2/O1/O2(最小化尺寸、最大化速度)或 /Ox 时有效strong>(启用大多数速度优化)。

此选项要求您使用 /O1/O2/Ox/Og 启用优化>.

在 Visual Studio 开发环境中设置此编译器选项

  1. 打开项目的“属性页”对话框。有关详细信息,请参阅使用项目属性。
  2. 展开配置属性,C/C++,然后选择优化。
  3. 修改内联函数扩展属性。

更多信息请阅读文章/Ob (Inline Function Expansion)

【讨论】:

  • 虽然这当然会使分析结果“更漂亮”,但它们也变得毫无意义。在 C++ 中禁用内联将极大地改变许多代码的性能特征,尤其是在使用模板时(容器,有人吗?)。
  • @milianw 是的,你的观点是正确的。但是所有的时间都会相对于每个over发生变化,函数总结果的关系会接近真实结果。我想它不应该是最终的分析迭代。在对内联函数进行分析和优化后,man 应该再次返回内联扩展。
  • @S.M.:你声称的不是真的。大量使用模板的函数将受到严厉惩罚,而不使用模板的函数不会显示任何速度差异。这会让新手感到困惑,并让他们再次相信 C 数组比使用向量更快......您也可以声称调试构建将比发布构建慢,但这也完全不正确.
猜你喜欢
  • 1970-01-01
  • 2023-04-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多