Bomi
分类: 系统运维
2012-02-05 00:42:52
By Darryl Gove、Chris Aoki, 8/18/08
配置文件反馈(Profile feedback)是一种有用的机制,它可以将代码的运行时信息提供给编译器。这些信息有助于显著提升应用程序的性能。与所有的优化方法一样,只有当配置文件反馈能够提高程序性能时才值得使用。
在为编译器提供训练数据(training data)时,需要小心选择典型的工作负荷。可以通过比较 tcov 或 Performance Analyzer 等工具收集到的配置文件来检查工作负荷的典型性。
简介在编译应用程序时,编译器将尽量选择最佳的指令和最佳的代码布局。这必须根据源代码做出决策。但源代码不包含与代码动态行为相关的信息,因此编译器必须通过启发式算法提供“最佳猜测”。
启发式算法可用于确定如何构造代码,哪些例程应该内联,哪些代码段需要频繁执行等诸多细节。
示例 1 展示了编译器可能会遇到的一个问题:
void calculate(...)
{
if(...some condition...)
{
//do calculation
...
}
A
else
{
//do caclulation
...
}
B
//do more work
...
}
代码示例 1
在示例 1 显示的代码中,编译器需要做一项有意义的决策;它可以采用不同的方法来构造代码。编译器应该将 A 或 B 设定为默认方式(这样可使路径更快),还是应该在构造代码时使两个代码分支具有相同的性能呢?
在编译器的众多决策中,合理安排 if 语句只是影响代码布局的决策之一。其他决策包括:
在 SPEC CPU2000 基准测试程序中使用配置文件反馈,可以将浮点集性能平均提高 7%,整数集性能提高 16%。对于各部分代码,性能提高情况各不相同,有些代码没有提高,有些则显著提高。
构建配置文件反馈
配置文件反馈的理念是让程序运行一段时间,并收集该程序的运行时信息。然后,编译器通过这些数据精选优化决策。
使用配置文件反馈的流程是:
这意味着,使用配置文件反馈之后,构建过程花费的时间是不使用配置文件反馈时的两倍。这是因为构建过程需要两次通过编译器,还需要应用程序运行一小段时间。因此,使用复杂的构建过程换取运行时的性能提升是值得的。
选择典型工作负荷
通过配置文件反馈构建应用程序要求使用典型训练工作负荷将应用程序的运行时行为通知给编译器。工作负荷的要点包括:
人们有时会担心使用错误训练工作负荷是否会导致性能变差。这是有可能的,出现这种情况一般有两个原因:
在这两种情况下,都可以增加另一个训练工作负荷来改进问题工作负荷的性能。也可以通过查看代码覆盖面或各个例程所花的时间来确定性能不同的原因。训练工作负荷造成其他工作负荷运行缓慢的情况是很少见的。更常见的情况是,训练数据显示某项特别的优化是多余的,或者使用附加的训练数据证明有必要进行优化并且会改进问题工作负荷的性能,而不影响其他工作负荷性能。
配置文件反馈的好处
编译器拥有的信息越多,就越有助于应用程序的优化。和所有优化方法一样,有些代码会得到很大获益,而有些则不会。这很大程度上由代码类型决定。
因配置文件反馈而受益的代码类型很可能是带有大量条件语句(if 语句)的代码。那些具有非常强的预测行为、且此行为对编译器来说不明显的代码将会获益最大。
这类代码的一个简单例子是程序中需要检验准确值的地方。编译器不能很容易地确定程序员希望检验通过还是不通过,因此一般会假设通过和不通过机会均等。然而,如果检验的是“合法数据”,大多时候代码中的数值是合法的,配置文件反馈就会使编译器知道这个情况,并且适当地优化代码。
配置文件反馈使程序性能得到提升的另一种情况是当配置文件可用于为编译器选择最佳的内联程序时。内联有两个好处,一个是减少了调用例程的开销,二个是公开了优化的更多机会。内联的缺点是它会导致代码数量增加;如果内联代码没用,那么代码数量的增加可能导致程序性能降低。配置文件反馈帮助编译器正确地选择频繁调用的例程作为侯选,同时拒绝很少调用的例程。
配置文件反馈的编译器标记
- xprofile 标记的作用是通知编译器构建应用程序并收集配置文件,或者构建应用程序并且使用现有的配置文件。在使用标记时,需要注意一些细微之处。
当程序执行时,-xprofile=collect:myapp将配置文件数据放置在当前目录的myapp.profile 目录下。类似地,-xprofile=collect:/tmp/myapp将配置文件数据放置在 /tmp/myapp 目录下。如果没有指定放置地点,就将配置文件数据放置在
.profile目录下, 是正在运行的可执行文件的名字。
-xprofile=use:/tmp/myapp会使用放置在 /tmp/myapp.profile 的配置文件数据。如果没有指定放置地址,编译器就会在当前目录下的 a.out.profile中寻找数据。注意这与 -xprofile=collect阶段是不同的行为。不同的原因是当采集数据时,配置文件采集器可以确定可执行文件的名字,而是当编译器使用配置文件数据构建新应用程序时,不知道用于收集配置文件的应用程序的名字。
注意:在构建可执行程序时,总为配置文件数据指定一个完整的地址是一个很好的习惯。
使用 -xprofile=collect 收集配置文件数据编译应用程序时,还是用低优化级别生成二进制代码,以便比使用优化二进制代码收集到更加详细的数据。生成的二进制代码有特殊的代码布局,这取决于源代码和构建时使用的标记。如果标记改变,代码布局也会改变。
注意: 除了参数 -xprofiler,最好在收集和和使用两个阶段都使用相同的标记。
当运行可执行文件时,配置文件数据被写入系统文档中。写入过程发生在运行的最后阶段,因此如果应用程序不能完成运行,那么也许不会有配置文件数据写入。如果应用程序多次运行,那么配置文件数据将会累积所有的运行结果。
如果源代码修改过,最好不要再用修改以前的配置文件数据。使用原来的配置文件数据,可能编译器不会报告错误,但是更可能导致编译器不能采用最优方案。
注意: 每次构建新的 - xprofile=collect,移除原有的配置文件数据,当源代码改变时,收集的新的配置文件数据,这是一个很好的习惯。
有几个编译器选项会使用配置文件反馈信息:
编译器标记 - xipo 和 - xcrossfile 执行 crossfile 优化 -- 意思是跨越多个源文件的优化。这种优化的一个例子是将一个程序从一个源文件内联入另一个源文件的代码中。有配置文件反馈提供信息,编译器就有了设置内联例程的好模型。
编译器标记 - xlinkopt 使编译器能够执行链接时间优化。编译的最后阶段使用生成代码的所有知识对代码布局做最后调整。这对可以通过代码布局将所有频繁执行的代码放在一起而使性能提升的大型代码很有用。
#include
#include
static unsigned f( unsigned *a0, unsigned *a1, unsigned *a2,
unsigned *a3, unsigned *a4, unsigned *a5)
{
unsigned result = 0;
if (a0 == NULL) {printf("a0 == NULL");} else {result += (*a0);}
if (a1 == NULL) {printf("a1 == NULL");} else {result += (*a1);}
if (a2 == NULL) {printf("a2 == NULL");} else {result += (*a2);}
if (a3 == NULL) {printf("a3 == NULL");} else {result += (*a3);}
if (a4 == NULL) {printf("a4 == NULL");} else {result += (*a4);}
if (a5 == NULL) {printf("a5 == NULL");} else {result += (*a5);}
return result;
}
void main(int argc,const char *argv[])
{
int i, j, niters = 1, n=6;
unsigned sum, answer = 0, a[6];
niters = 1000000000;
if (argc == 2) { niters = atoi(argv[1]); }
for(j=0; j
a[j] = rand();
answer += a[j];
}
for(i=0; i
else { printf("error sum=%u, answer=%u", sum, answer); }
}
代码示例 2 - 使用配置文件反馈提升性能演示
示例 3 显示了未使用配置文件反馈编译和运行程序的结果。
$ cc -O -o example example.c
$ timex example 1000000000
answer = 86902
real 43.87
user 43.28
sys 0.00
代码示例 3 -不使用配置文件反馈的编译和运行
示例 4 显示了通过配置文件反馈编译该代码的过程。注意,其中某个程序训练运行使用的迭代远少于程序的主循环。
$ cc -O -xprofile=collect:./example -o example example.c
$ example 100
answer = 86902
$ cc -O -xprofile=use:./example -o example example.c
$ timex example 1000000000
answer = 86902
real 34.52
user 33.93
sys 0.01
代码示例 4 -使用配置文件反馈的编译和运行
两段代码在运行时间上的 10 秒差距代表了 25% 的性能提升。显然,这个特殊的例子演示了配置文件反馈优化的作用,但是它所揭示的原理也出现在大多数代码中。
关于作者
Darryl Gove 在 Sun Microsystems的编译器性能工程部门担任高级工程师,负责分析和优化现在及未来 UltraSPARC系统中的应用程序的性能。他拥有英国南安普敦大学运筹学专业的硕士及博士学位。在加入 Sun 之前,他曾在英国从事各种软件架构和开发工作。
Chris Aoki 是 Sun SPARC 编译器后台小组的一名工程师。他曾在 Sun 负责几代编译器技术的代码生成和优化工作。目前,他的项目主要涉及为基于反馈的优化提供编译器和运行库支持。