VS2019中对C++内联器的改进:Zipliner

VS2019中对C++内联器的改进:Zipliner

作者:BlogUpdater |  时间:2020-01-08 |  浏览:809 |  评论已关闭 条评论

官一下宣
Visual Studio 2019 v16.3和v16.4包含了对C++内联器的一系列改进,其中包含这么一条:具备对某些经过优化后的代码进行内联的能力,我们称之为”Zipliner”。根据你的应用的不同,你可能会看到一些较小的代码质量改进或者编译时间的显著缩短。

C2内联器
Terry Mahaffey在之前的一篇文章”Visual Studio’s inlining decisions”中给出了VS内联决策的概述。以下是对于这项改进比较相关的要点:
1. 内联器是以递归的方式运行的,在某些情况下,它可能会重新做它之前做过的事情。内联决策是上下文敏感的并且它并不总是对同一个函数做出相同的决策。
2. 内联器在工作过程中十分关注资源的消耗。所以它会在生成的可执行文件的大小和运行时性能之间做出艰难的平衡。
3. 在内联器看来,它所面对的世界都是”未经优化”的。它并不了解隐藏在代码背后的逻辑细节,比如Copy propagation和Dead control path之类的。

现代C++
不幸的是,现有的大多数代码模式和惯用法都引入了泛型编程的理念。考虑如下的代码(来自Eigen库):

它会调用下面代码中的innerSize函数:

outerStride实例化后实际上只是返回它的一个数据成员。因此,它会是一个极好的内联展开对象。但是对于模块中每一次对outerStride的调用,编译器都必须评估和展开多达18个被它调用的函数。
这将显著的影响编译器的编译时间,并大大增加了内联器的资源消耗。另外,更坏的消息是,对”rows”和”cols”的调动都是内联的,即使他们它们并不会被执行。

如果编译器只是对2行成员执行内联优化,那就会好得多了,如下所示:

对经过优化的IR进行内联
对于有些函数,内联器现在可以对经过优化的IR进行展开,而不需要先获取IR然后重新展开被调用函数。这会带来两个好处:一是可以加快调用树的展开,二是有助于内联器更加精准的评估其资源消耗。

第一,优化器在第一次编译时将作出快速展开outerStride的决定(还记得吗,c2.dll会在编译函数的调用者之前编译函数本身)。然后,内联器可能会将对outerStride的实例化调用替换为现场访问。

这个能被快速展开的函数对象通常是一个没有本地变量的”叶子函数”,最多只会有两个不同的参数或者全局变量等。在实际的项目中,他们可能是一些getters或者setters。

带来的好处
还有很多类似于outerStride的例子。如果你的代码大量的使用到了Eigen库,你会发现编译时间有显著的缩短,编译吞吐量将有25%到50%的提升。

新的Zipliner处理提升编译性能,同时也能让内联器更准确的评估资源消耗。一直以来,Eigen库的开发者都知道MSVC不会内联他们设立的规范(详情请看EIGEN_STRONG_INLINE)。Zipliner的出现应该会让他们缓解这种焦虑。

试试看!
Zipliner默认在VS2019 v16.3中启用,同时在v16.4中得到了一些新的优化。

总结
时至今日,我还是没有足够的勇气去探索编译器的世界。还是写几年HelloWorld把基础打好先吧。
感谢阅读(毕竟不是太容易懂)。

标签:

评论已关闭。