编译器完成的配置文件引导优化是否会损害分析数据集未涵盖的案例?

时间:2011-10-20 10:42:45

标签: performance optimization language-agnostic profiling compiler-optimization

这个问题不是特定于C ++,AFAIK像Java RE这样的某些运行时可以动态地进行分析引导优化,我也对此感兴趣。

MSDN describes PGO是这样的:

  1. 我检测程序并在Profiler下运行,然后
  2. 编译器使用分析器收集的数据自动重新组织分支和循环,以便减少分支错误预测,并且大多数情况下运行代码紧凑地放置以改善其位置
  3. 现在显然,分析结果将取决于使用的数据集。

    通过正常的手动分析和优化,我会发现一些瓶颈并改善这些瓶颈,并可能保持所有其他代码不受影响。 PGO似乎改进了经常运行的代码,但却使得很少运行代码的速度变慢。

    现在如果经常在另一个程序将在现实世界中看到的数据集上运行那么慢的代码呢?与没有PGO编译的程序相比,程序性能是否会降低,退化的可能性有多大?换句话说,PGO是否真的提高了我的分析数据集的代码性能,并可能使其他数据集的性能恶化?有真实数据的真实例子吗?

2 个答案:

答案 0 :(得分:9)

免责声明:我没有做过更多关于PGO的事情而不是阅读它并尝试了一个示例项目以获得乐趣。以下很多内容都是基于我的经验,即非PGO"优化和有根据的猜测。 TL; DR在下面。

This page列出了PGO所做的优化。让我们逐个看一下(按影响分组):

  

内联 - 例如,如果存在经常调用函数B的函数A,并且函数B相对较小,则配置文件引导的优化将在函数A中内联函数B.

     

注册分配 - 使用配置文件数据进行优化可以获得更好的寄存器分配。

     

虚拟呼叫推测 - 如果虚拟呼叫或通过功能指针的其他呼叫经常以某个功能为目标,则配置文件引导优化可以插入有条件执行的直接呼叫经常有针对性的功能,可以内联直接调用。

这些显然改善了预测,无论某些优化是否得到回报。没有直接权衡非配置代码路径。


  

基本块优化 - 基本块优化允许在给定帧内暂时执行的常用基本块放置在同一组页面(局部性)中。这样可以最大限度地减少使用的页数,从而最大限度地减少内存开销。

     

功能布局 - 根据调用图和配置文件调用者/被调用者行为,往往沿同一执行路径的函数放在同一部分中。 < / p>      

死代码分离 - 在分析期间未调用的代码将移动到附加到该组部分末尾的特殊部分。这有效地将此部分排除在常用页面之外。

     

EH代码分离 - 当配置文件引导的优化可以确定异常仅在异常条件下发生时,异常执行的EH代码通常可以移动到单独的部分。 / em>的

所有这些都可能会减少非配置代码路径的位置。根据我的经验,如果此代码路径具有超过L1代码缓存的紧密循环(甚至可能使L2陷入困境),则影响将是显着或严重的。这听起来就像应该包含在PGO配置文件中的路径:)

死代码分离可以产生巨大的影响 - 两种方式 - 因为它可以减少磁盘访问。

如果您依赖快速处理异常,那么您做错了。


  

尺寸/速度优化 - 程序花费大量时间的功能可针对速度进行优化。

现在的经验法则是默认优化大小,并且只在需要时优化速度(并验证它有帮助)。原因是代码缓存 - 在大多数情况下,由于代码缓存,较小的代码也将是更快的代码。所以这种自动化应该手动完成。与全局速度优化相比,这只会在非常典型的情况下(&#34;奇怪的代码&#34;或具有异常缓存行为的目标机器)减慢非配置代码路径。


  

条件分支优化 - 使用值探测器,配置文件引导的优化可以查找switch语句中的给定值是否比其他值更频繁地使用。然后可以从switch语句中提取该值。使用if / else指令可以完成相同的操作,优化器可以对if / else进行排序,以便if或else块首先放置,具体取决于哪个块更频繁为真。

除非你提供错误的PGO信息,否则我会在#34;改进预测&#34;下提交。

这可以支付很多费用的典型情况是运行时参数/范围验证以及在正常执行中永远不应采用的类似路径。

破案将是:

if (x > 0) DoThis() else DoThat();

在相关的紧密循环中仅分析x&gt; 0个案例。


  

记忆内在性 - 如果可以确定是否经常调用内在函数,则可以更好地确定内在函数的扩展。还可以根据移动或副本的块大小优化内在函数。

同样,大多数情况下信息更好,对未经测试的数据进行处罚的可能性很小。

示例: - 这是一个受过良好教育的猜测&#34;,但我认为这对整个主题来说非常具有说明性。

假设你有一个memmove总是在长度为16字节的良好对齐的非重叠缓冲区上调用。

可能的优化是验证这些条件,并在这种情况下使用内联MOV指令,仅在不满足条件时调用常规memmove(处理对齐,重叠和奇数长度)。

在复制结构的紧密循环中,好处可能是显着的,因为您改善了局部性,减少了预期的路径指令,可能有更多的配对/重新排序机会。

然而,惩罚相对较小:在没有PGO的一般情况下,你要么总是调用完整的memmove,要么nline完整的memmove实现。优化为一些相当复杂的东西添加了一些指令(包括条件跳转),我假设最多只有10%的开销。在大多数情况下,由于缓存访问,这些10%将低于噪声。

但是,如果经常采取意外分支预期案例的附加说明以及默认情况下的说明推送紧密循环,则会产生轻微影响。 L1代码缓存

请注意,您已经处于编译器可以为您做的限制之内。与代码高速缓存中的少量K相比,可以预期附加指令是几个字节。静态优化器可能会达到相同的命运,具体取决于它可以提升不变量的程度 - 以及你拥有多少。


结论:

  • 许多优化都是中立的。
  • 某些优化可能会对非配置文件代码路径产生轻微的负面影响
  • 影响通常远小于可能的收益
  • 极少数情况下,其他有影响的病因可以强调小的影响
  • 很少有优化(即代码部分的布局)会产生很大的影响,但是可能的增益再次显着增加

我的直觉会进一步声称

  • 从整体上看,静态优化器至少同样可能会创建一个病态案例
  • 即使PGO输入错误,实际上销毁表现也很难。

在那个级别,我会比PGO优化失败更害怕PGO 实现错误/缺点。

答案 1 :(得分:1)

PGO肯定会影响运行频率较低的代码的运行时间。毕竟你正在修改一些函数/块的局部性,这将使现在在一起的块更加缓存友好。

我所看到的是团队确定了他们的高优先级场景。然后他们运行这些来训练优化分析器并测量改进。您不希望在PGO下运行所有​​方案,因为如果您这样做,您可能也不会运行任何方案。

在与性能相关的所有内容中,您需要在应用之前进行测量。通过使用PGO培训,了解最常见的情景,看看它们是否有所改善。并且还测量不太常见的情况,看它们是否会退化。