我们以前在博客上讨论过概要文件引导优化的好处。我们收到的最大的反馈之一是,插入指令的二进制文件速度太慢,这使得训练某些类的应用程序(如游戏)非常困难。
这是我们在VS 2015中尝试解决的问题—对插入指令的二进制文件和相关PGO运行时的代码生成进行了一系列幕后更改,以提高运行时性能并最小化插入指令的开销。在某些情况下,我们已经看到高达30%的吞吐量增长。每个人都可以免费得到这个——除了在VS 2015上使用PGO之外,你不需要做任何事情。但是我今天想谈的是一些我们在默认情况下无法完全打开的优化,以及为什么,以及它们相关的命令行选项。
要查看,要在VS 2013中使用PGO,请将/LTCG:PGI传递给链接器以生成插入指令的二进制文件,并将/LTCG:PGU传递给链接器以生成PGO优化的二进制文件。
在VS 2015中,特定于PGO的选项集中在一个顶级交换机上,以链接到link.exe,并带有几个自己的子交换机。从链接/?:
/GENPROFILE[:{COUNTER32 | COUNTER64 | EXACT | MEMMAX=#| MEMMIN=#| NOEXACT | NOPATH | notrackh | PATH | PGD=filename | TRACKEH}]
这是必要的,它使它成为一个顶级开关,以便给它的子开关。GENPROFILE的第一条规则:所有默认行为都与VS 2013相同。传递/GENPROFILE(没有子开关)与VS 2013和VS 2015中的/LTCG:PGI完全相同,因此:出于兼容性原因,我们仍然接受旧的开关。
计数器32与计数器64 :COUNTER64是默认值–对探测计数器使用64位值,COUNTER32将使用32位值。如果任何一个探针的值接近或超过2^32,这显然是很重要的——但事实证明,几乎没有探针能做到这一点。与32位增量相比,64位增量的开销似乎不太大,但请记住,在插入指令的构建中有很多探测,大约每两个基本块就有一个探测,因此在x86上,代码大小和性能的开销都会增加。
那么你怎么知道什么时候使用COUNTER32是安全的呢?我们在pgomgr/summary中添加了一些有用的输出:
C:emp>pgomgr foo.pgd/摘要
Microsoft(R)Profile Guided Optimization Manager 14.00.23022.0版
版权所有(C)Microsoft Corporation。版权所有。
PGD文件:foo.PGD
2014年5月3日00:20:07模块计数:1 函数计数:11362 弧数:12256 值计数:377
阶段名称:
最大探测计数器:0x0000000000DE0467(0.34%%)
考虑/GENPROFILE:COUNTER32以提高训练性能。
它告诉我们,训练场景中的最大探测计数器值是DE0467(1400万),这是32位计数器空间(40亿)的0.34%。还差一点呢。基于此,您将看到输出计数器32。
使用COUNTER32绝大多数的训练场景都是非常好的——事实上,在内部我从未见过一个不是这样的场景。但是,您可以想象32位计数器溢出的后果是非常糟糕的,而且现有的外部客户很可能有需要64位计数器的培训场景,因此COUNTER64需要保留默认值。
精确与非精确 :NOEXACT是默认值。这是旧的/POGOSAFEMODE开关的重命名版本,已被弃用。确切的方法是对探针使用线程安全联锁增量,而当指定NOEXACT时,我们不使用。如果您有一个多线程的程序,并且您的训练质量因此受到影响,那么EXACT是一个好主意/出于兼容性原因,POGOSAFEMODE仍然被接受。
MEMMAX=#和MEMMIN=#: 这些值指定内存中训练数据的最大和最小内存保留大小(以字节为单位)。在内部,PGO使用启发式方法来估计所需的内存量并保留空间。因为以后不太可能扩展空间(保留需要是连续的和稳定的),所以这个初始保留非常激进。在某些情况下,尤其是在同一进程中存在多个插入指令的二进制文件时,这可能会导致地址空间不足,并最终导致内存不足错误崩溃。
MEMMAX和MEMMIN提供了一种方法来指定PGO在估计所需内存时内部使用的启发式的上限和下限。PGO仍会作出估计,但适当尊重MEMMAX和MEMMIN值。
那么你怎么知道在这里使用什么价值呢?我们在这里也添加了一些有用的输出,但这次是在合并时:
C:emp>pgomgr/合并foo.pgd
Microsoft(R)Profile Guided Optimization Manager 14.00.23022.0版
版权所有(C)Microsoft Corporation。版权所有。
正在合并foo!1.pgc公司
福!1.pgc公司: 使用14.7%(3608/24576) 总保留空间的。 由于溢出,0.0%的计数被丢弃 .
在这个小例子中,内存保留大小是24576字节,其中训练只需要3608字节。如果这些值在所有PGC文件中都是一致的,那么在生成插入指令的二进制文件时,可以安全地指定较低的MAXMEM大小。另一个输出估计如果可用空间被填满,数据丢失的数量。如果此值不再为0%%,则可能需要指定更高的MEMMIN大小。
我怀疑没有多少人会需要这个选项,但如果你发现自己在训练中遇到了记忆问题,这是值得研究的。之所以添加它,是因为在遇到内存问题时,唯一的其他选择是将多个二进制文件的训练分为多个单独的训练运行,这会带来与之相关的人力成本。
路径与无路径: 路径是默认值。路径评测是指PGO为函数的每个唯一路径保留一组单独的计数器,从而在做出内联决策后,能够更好地进行内联决策,并获得更准确的概要数据。这将导致更好的总体代码生成。
那你为什么要关掉这个?好吧,这里的内存开销很高:想象一下程序中对给定函数的所有不同的唯一调用路径——通过路径分析,我们为每个函数保留一组单独的计数器!没有路径,我们只保留一个。除了内存开销外,在函数prolog期间查找正确的计数器集还存在运行时开销。
如果您的内存使用率太高,并且插入指令的二进制文件的运行时性能太差,以至于您考虑根本不使用PGO,请尝试NOPATH。
我们非常喜欢路径分析,并度量与非路径分析相比的显著收益,因此我们不愿意在默认情况下关闭它。然而,我们确实希望人们使用PGO,而且非路径分析仍然比LTCG有显著的优势。所以这里是最后的选择。
TRACKEH与NOTRACKH之比较: TRACKEH是默认值。这基本上意味着每个调用站点周围都有两个计数器,一个在调用之前,一个在调用之后,以便在调用抛出异常并且控制流在其他地方恢复时保持准确的计数。如果您的二进制文件通常不使用EH,或者在您运行的训练场景中不使用EH,那么您可以安全地将其关闭,以忽略这些调用探测较小的代码大小和速度获胜。默认情况下不启用此选项,因为启用此选项时,EH会损害训练精度。
PGD=路径 :与/EXACT类似,这是旧的/PGD开关,从顶级开关降级为/GENPROFILE的子开关/出于兼容性原因,仍然接受PGD。
这样就涵盖了个人资料。您可能会注意到另一个看起来非常类似的开关,/FASTGENPROFILE:
/FASTGENPROFILE[:{COUNTER32 | COUNTER64 | EXACT | MEMMAX=#| MEMMIN=#| NOEXACT | NOPATH | notrackh | PATH | PGD=filename | TRACKEH}]
事实上,这是完全一样的:唯一的区别是默认值。GENPROFILE默认为COUNTER64、NOEXACT、PATH、TRACKEH(相当于VS 2013 behavior),其中as FASTGENPROFILE默认为COUNTER32、NOEXACT、NOPATH和notrackh。
对于使用配置文件,我们有一个新的/USEPROFILE开关:
/USEPROFILE[:PGD=filename]
这相当于VS2013中的/LTCG:PGU(正如您所料,/LTCG:PGU在兼容性方面仍然是可以接受的)。这里的PGD选项与/GENPROFILE相同,也就是说它是VS2013中的旧/PGD开关。
如果您当前或计划使用IDE中的PGO:我们目前尚未更新属性页以接受这些新的概要文件引导优化开关,它们仍然指向我们在VS2013中使用的那些开关。这是目前我们的雷达和这些更改的属性文件应该弹出一个VS 2015更新。现在,请使用链接器命令行属性页。
所以你有了它。在VS 2015中,我们清理了PGO交换机的组合,并提供了一系列选项,用于控制插入指令的PGO构建的代码生成和训练保真度。有很多幕后的改变,这些改变并没有影响到培训质量。所以,让PGO在VS 2015一试,我们很乐意听到您的反馈!