如果编译器能够证明数据或函数永远不会被引用,那么编译器只能优化掉数据或函数。在一个非- LTCG公司 编译(即使用 全程序优化(WPO) 禁用)编译器的可见性仅限于单个模块(.obj),因此对于具有全局作用域的数据和函数,编译器永远不会知道其他模块是否将使用它们。因此,编译器永远无法优化它们。
链接器可以很好地查看将链接在一起的所有模块,因此链接器可以很好地优化掉未使用的全局数据和未引用的函数。但是,链接器在节级别进行操作,因此如果未引用的数据/函数与节中的其他数据或函数混合,链接器将无法将其提取并删除。为了使链接器能够删除未使用的全局数据和函数, 我们需要将每个全局数据或函数放在一个单独的部分中,我们称之为“小部分” 通信 “.
今天使用( /葛兰素 )编译器开关指示编译器 只有 以打包函数或COMDAT的形式打包单个函数,每个函数都有自己的节头信息。这将启用功能级链接并启用 链接器优化ICF(将相同的COMDAT折叠在一起)和REF(消除未引用的COMDAT) . 在VS2013中(下载 在这里 ), 我们还引入了一个新的编译器开关(/Gw),它扩展了数据的这些优点(即链接器优化)。
为了进一步理解,让我们看下面的一个例子。您可以自己尝试一下:
图1:使用/Gy编译器标志触发的链接器优化(即REF)
如果用户编译图1中的代码段(foo.cpp和bar.cpp)时使用/不使用/Gy编译器标志和后续链接( 链接/opt:ref /map foo.obj酒吧.obj )在启用链接器优化的情况下(即/opt:ref),在生成的结果映射文件中,可以观察到函数“foo”已被删除。但是,仍然可以观察全局数据“globalRefCount”在映射文件中的出现。如前所述,/Gy只指示编译器将单个函数打包为COMDAT,而不是数据。 另外,提供/Gw编译器标志 除此之外,/Gy标志还允许将数据和函数打包为COMDAT,从而允许链接器同时删除这两个数据和函数 函数“foo”和“globalRefCount”。
由于启用了LTCG,编译器的可视性超出了单个模块的可视性,因此要理解用户在WPO构建中启用此功能可能会获得什么好处可能并不明显。例如,如果使用WPO编译图1所示的示例,编译器可以优化掉函数’foo’和数据实体’globalRefCount’。但是,如果上面描述的示例稍微更改为下图所示的内容,那么仅仅用WPO编译是没有帮助的。一旦获取了全局变量的地址,编译器就很难证明在指针的神奇世界中,全局变量不是由其他函数读取或写入的,而且即使启用了WPO,编译器也会放弃优化这些场景。
但是在/Gw的帮助下,链接器仍然可以在这里删除未引用的数据实体,因为链接器的REF优化不会被地址获取之类的事情阻止。链接器精确地知道它是否被引用,因为任何对全局数据的引用都会显示为链接器修复(coff重定位),而这与地址是否被引用无关。下面的例子看起来像是手工制作的,但是可以很容易地翻译成真实世界的代码。
图2:获取全局变量的地址
有和只有 WPO公司 已启用生成, 我们还受益于链接器ICF优化 (链接/ltcg/map) /opt:icf foo.obj酒吧.obj/out:example.exe) 当/Gw开启时,除REF外。如果我们看一下下面图3中描述的示例,如果没有/Gw,在最后的图像中会有两个相同的’const int data1[],const int data2[]。如果启用“/Gw”,“data1”和“data2”将折叠在一起。 请注意 ,ICF优化将仅应用于相同的通信数据,其中它们的地址不被占用,并且它们是只读的。如果一个数据不是地址获取的,那么用ICF破坏地址唯一性不会导致任何可观察的差异,因此它是有效的,符合标准。
图3:数据通信的连接器ICF优化
总而言之,使用“/Gw”编译器开关,我们现在可以使链接器优化(REF和ICF)也可以处理未引用和相同的数据通信。对于已经利用了功能级链接的人来说,这应该很容易理解。我们已经看到了两位数的收益(%)在大小减少时,启用此功能来构建二进制文件,这构成了一些高容量的微软产品,所以我鼓励你们也尝试一下,并返回给我们。 在这一点上,你应该有一切你需要开始! 此外,如果您想让我们在博客上介绍一些其他的编译器技术,请让我们知道我们总是有兴趣从您的反馈中学习。