对于VisualStudio 2013,我们继续改进Visual C++编译器执行的分析,以便它能够生成比以前运行得更快的代码。在本博客中,我们将重点介绍VisualStudio2013为您准备的许多改进。这个博客旨在为您提供一个关于我们最近添加的所有好处的概述,这将有助于使您的代码运行得更快。我们已经将这些改进应用到下面列出的几个主要场景中,但在开始之前,让我们回顾一下现有的性能。
自由表演概述
Visual C++编译器提供了许多优化标志。 /O标志 ,除了/Od)。/O优化标志在每个模块(compliand)的基础上执行优化,这意味着在使用/O标志时不执行过程间优化。这样做的主要目的是为用户提供性能/代码大小和编译时间之间的平衡。
Visual Studio 2013开箱即用随附 全程序优化 (WPO)为“发布”生成配置启用(/GL或/LTCG生成标志)。整个程序优化允许编译器使用程序中所有模块的信息执行优化。这尤其是在其他优化中允许跨过程内联和跨函数边界优化寄存器的使用。WPO确实以增加构建时间为代价,但它为应用程序提供了最大的性能。
图1:编译单元和整个程序优化(/O2和/GL)
作为这个场景的一部分,用户需要做的就是使用VisualStudio2013重新编译他们的应用程序,以从下面提到的所有smarts中获益。让我们开始吧!
完全循环嵌套的置换
内存(工作集、缓存和访问的空间/时间局部性)*在应用程序性能方面非常重要。如果您有一个嵌套循环,并且正在处理太大而无法放入处理器的三级缓存中的大型数组,那么代码运行的速度主要取决于从内存中获取所需的时间,而不是在循环体中执行的实际计算,有时,通过更改嵌套循环的顺序,我们可以大大加快速度。要了解有关此优化的更多信息,请参阅 埃里克·布鲁默的 在//build, 本机代码性能和内存:CPU中的大象 .
自动矢量化 ++
Visual C++ 2013编译器继续演化代码,我们可以矢量化,因此编译器现在矢量化包含MI/MAX和其他操作的循环。编译器现在也能够成功地将(以和或积为例)归约到数组元素中,而不是一个简单的变量中。编译器还特别注意代码中表示“restrict”的位置,这有助于省略先前发出的运行时检查,以检查是否存在抑制矢量化的潜在指针重叠。最后,我们还介绍了一种称为“语句级”矢量化的技术,稍后我们将对此进行更深入的研究。为了让您更了解这些改进是如何发挥作用的,让我们看几个例子:
例1: 矢量化C++标准模板库代码模式
我们已经努力使自动矢量化对C++标准模板库在其实现中使用的各种代码模式友好。在描述上一版本的自动矢量化时,我们的示例都显示了counted for循环,遍历数组。但是看看上面的例子1——一个while循环而不是counted for循环——没有眼睛,也没有鸟!也没有方括号表示数组索引-只是一堆指针!然而,我们为您成功地将其矢量化。
例2: 语句级矢量化
如果你看一下这个例子,这里没有循环,但是编译器认识到我们在做相同的算术(取结构中相邻字段的倒数),它利用处理器的向量寄存器和指令对代码进行向量化。
距离传播
我们添加的另一个优化称为“距离传播”。有了这种优化,编译器现在可以跟踪函数执行时给定变量可能采用的值的范围。这允许编译器有时省略case语句的整个arm,或者嵌套if-then-else块,从而删除冗余测试。
/Gw编译器开关
如果编译器能够证明数据或函数永远不会被引用,那么编译器可以优化掉数据或函数。但是,对于非WPO构建,编译器的可见性仅限于单个模块(.obj),禁止它进行此类优化。不过,链接器可以很好地查看将链接在一起的所有模块,因此链接器可以很好地优化掉未使用的全局数据和未引用的函数。链接器虽然在节级别进行操作,因此如果未引用的数据/函数与节中的其他数据或函数混合,链接器将无法将其提取并删除。为了使链接器能够删除未使用的全局数据和函数, 我们需要将每个全局数据或函数放在一个单独的部分中,我们称之为“小部分” 通信 “.
今天使用( /葛兰素 )编译器开关指示编译器 只有 以打包函数或COMDAT的形式打包单个函数,每个函数都有自己的节头信息。这将启用功能级链接并启用 链接器优化ICF(将相同的COMDAT折叠在一起)和REF(消除未引用的COMDAT) . 在VS2013中(下载 在这里 ), 我们还引入了一个新的编译器开关(/Gw),它扩展了数据的这些优点(即链接器优化)。 值得注意的是,这种优化还为 WPO/LTCG公司 构建。了解更多信息和深入了解 “/Gw”编译器开关 ,请看我们的一个 以前的博客文章 .
向量调用约定(/Gv编译器开关)
对于Visual C++ 2013,我们介绍了一种新的调用约定,称为“向量调用约定”,用于x86/x64平台。顾名思义,向量调用约定的重点是在传递向量类型参数时利用向量寄存器。 使用 __矢量调用 加速传递多个浮点或SIMD向量参数的函数,并利用寄存器中加载的参数执行操作。与现有的调用约定(例如,x64上的fastcall)相比,向量调用约定不仅节省了为执行相同操作而发出的指令数,而且还节省了用于创建传递向量参数所需的临时缓冲区的堆栈分配。 在不改变源代码的情况下使用向量调用约定来验证性能增益的一种快速方法是使用/Gv编译器开关。然而,理想的方法仍然是用 __矢量调用 关键字,如下例所示:
图5:向量调用约定示例
要了解更多有关“向量调用约定”的信息,请查看我们的 以前的博客文章 和文档 MSDN公司 .
轮廓,编译和微笑多一点
到目前为止,我们已经讨论了为VisualC++ 2013添加的新优化,为了利用这些优化,您需要重新编译应用程序,但 如果您关心一些额外的性能,那么这一节是为您准备的 . 要获得应用程序的最大性能/代码大小,请使用 轮廓引导优化 (PGO)(图6)。同样,这种额外的性能是以额外的构建时间为代价的,并且需要为应用程序启用整个程序优化。
图6:轮廓引导优化
PGO是一个运行时编译器优化 它利用从运行重要的或以性能为中心的用户场景收集的配置文件数据来构建应用程序的优化版本。PGO优化与传统的静态优化相比有一些显著的优势,因为它们基于应用程序在生产环境中可能的执行方式,从而允许优化器针对较热的代码路径(常见用户场景)优化速度,并针对较冷的代码路径(不太常见的用户场景)优化大小从而为应用程序生成更快更小的代码,从而显著提高了性能。有关PGO的更多信息,请查看一些早期的 博客帖子 .
在Visual C++ 2013中,我们继续提高PGO的功能和数据布局的能力,因此生成的PGO代码运行得更快。除此之外,我们还改进了对PGO确定cold或scenario dead的代码段执行的优化。因此,进一步降低了影响冷代码段或未经训练的代码段的性能的风险。
传统PGO用户的一致痛点是他们无法验证执行PGO的训练阶段,因为PGO实现的性能增益与应用程序的训练成正比,这成为先前Visual C++版本中已经丢失的一个极其重要的特征。从Visual Studio 2013开始,如果用户为PGO优化构建创建示例配置文件,则“调用树”中会亮起额外的列,这些列指定是否对特定函数进行了PGO化,以及除此之外,是否对特定应用程序的大小或速度进行了优化。PGO编译的函数被认为是场景热的速度和其余的编译的大小。下面的图7列出了vspx配置文件中亮起的额外PGO诊断信息。要了解有关如何启用此方案的更多信息,请查看以下内容 博客 这是早些时候出版的。
图7:VSPX配置文件中的配置文件引导优化诊断信息
最后,针对轮廓导引优化问题,设计了一个开箱即用的原型系统 插件 最近也推出了,现在可以从VSGallery下载(下载 在这里 ). 该插件安装并集成到“性能和诊断”中心。 该工具旨在改进 经验 在Visual Studio中为本机应用程序执行PGO的方法如下:
- 旨在提供 引导式体验 通过PGO过程的各个阶段(仪表、培训和优化)
- 除此之外,PGO工具还将提供当前仅在从命令行使用PGO时公开的功能。这包括能够训练不相交的训练集和使用PGO实用程序,如’ pgomgr公司 ‘查看和 分析培训质量 为PGO的训练阶段执行。
- 工具 介绍了为Windows应用商店应用程序执行PGO的功能 针对x86和x64应用程序。
下面是概要文件引导优化工具的快照,它描述了为进一步验证概要文件引导优化的培训阶段而发出的额外诊断信息。
图8:VSGallery中的Profile-Guided优化工具
总结
这个博客应该提供一些我们在Visual C++编译器中添加的一些东西的概述,它将帮助你的应用程序更快。对于我们所做的大部分工作(特别是自动矢量化) ++ ),你所需要做的就是重新构建你的应用程序,然后微笑着说,如果你正在寻找一些额外的性能提升,请尝试一下Profile-Guided Optimization(PGO)! 在这一点上,你应该有一切你需要开始! 此外,如果您想让我们在博客上介绍一些其他编译器技术或编译器优化,请告诉我们,我们一直有兴趣从您的反馈中学习。