- 注册时间
- 2007-12-27
- 最后登录
- 1970-1-1
- 威望
- 星
- 金币
- 枚
- 贡献
- 分
- 经验
- 点
- 鲜花
- 朵
- 魅力
- 点
- 上传
- 次
- 下载
- 次
- 积分
- 40142
- 在线时间
- 小时
|
楼主 |
发表于 2008-3-13 13:36:00
|
显示全部楼层
Profiling Guided Optimization
前一段时间有次在CSDN中看到有人说Intel编译器支持PGO,而gcc只有最新的版本才开始支持。一时没有看懂PGO是什么,查了一下google才知道是指
Profiling Guided Optimization。感觉挺惊讶,gcc难道以前的版本都不支持Profiling?
Profiling翻译成中文比较难,有可能根本就没有人翻译过。这里我们把它翻译成“普查”或“数据普查”吧。
比如说有下面一段程序代码- if(a<b){
- foo();
- ...
- }else{
- bar();
- ...
- }
- ///code after if
复制代码 其中foo和bar都是一段包含一定代码的函数。
对于这个分支语句,编译器如果能够知道实际上大部分情况(比如大于99%的情况)a都不小于b,那么对于这样的代码,我们可以对条件语句的假分支(也就是
调用bar()函数的分支特别优化,比如这里可以将函数bar()展开,从而可以减少一次函数调用。另外,对于真分支,我们知道这个代码即使放在这里,也很少
被执行,反而在代码被执行的时候,由于最近被执行的代码需要被装载入一级代码缓存,这段真分支代码由于靠近被执行的代码,也会被装载从而浪费了很多
一级代码缓存空间,影响了程序的效率。所以通常在编译器知道真分支将很少被执行的情况,编译器可以做如下的优化
i)交换真假分支的位置,代码变为- if(a>=b){
- bar();
- ...
- }else{
- foo();
- ...
- }
- ///code after if
复制代码 ii)将几乎不被执行的假分支(编译器中称为冷门代码(Cold Path))移到偏远的地方,从而不会影响代码缓存的效率,代码变成- if(a>=b){
- bar();
- ...
- }else{
- goto cold_path;
- }
- code_after_if:
- ///code after if
- ...
-
- cold_path:
- foo();
- ...
- goto code_after_if;
复制代码 所以我们知道,如果编译器能够知道程序流程图各个不同分支在实际运行中被执行的效率情况,那么编译器将可以更好的做优化。
为此,很多编译器提供了Profiling功能,也就是先让编译器根据源代码产生一段不一定优化过的程序,这个程序除了能够正常执行这个代码的功能外,
另外加入一些对程序行为的统计代码。
然后用户可以设计一些比较典型的输入数据,利用这些输入数据运行这个代码,而这段代码在运行过程中就会产生一些统计信息,相当于于对程序运行
中一些行为进行普查。
此后,编译器可以再次编译源代码,而编译过程中,会读入前面运行过程中产生的统计信息。有了这些统计信息,编译器就可以产生优化的更好的代码了。
所以通常,Profiling Guided Optimization至少需要编译程序两次以上,而中间还需要运行程序若干此,在运行过程中会产生中间文件,用于保存统计信息。
通常Profiling主要用于产生像上面例子中的信息,也就是程序流程图中各个分支被执行的频率。这个过程,我们通常称为edge profiling. 也就是其实
我们统计的是流程图中各条边的频率。
而对于编译器来说,要产生一个可以统计edge profiling的程序很简单,只需要在源代码中插装一些代码就可以,比如上面的代码,编译器可以临时产生两个
全局变量int true_count;和int false_count;
然后将源代码改为:- if(a>=b){
- true_count++;
- bar();
- ...
- }else{
- false_count++;
- foo();
- ...
- }
- ///code after if
复制代码 就可以了,然还重新定义标准C中at_exit函数,在其中输出true_count和false_count到一个事先指定的文件中就可以了。
而同样,对于循环,我们可以用这种方法统计出一个循环平均一次执行迭代次数是多少等信息。
不过上面插装过程只是一个简单的描述,实际过程会更加复杂。
比如如果上面这段代码由于非常热门,那么它就有可能在一次运行过程中被反复执行的次数超过了2^32。如果这样,那么true_count和false_count就会发生
溢出。这种情况非常糟糕,我们正好对热门代码的统计情况出了错,这回导致误导编译器,产生效率反而比不优化前更加差的代码。
那么如果为了防止溢出,我们将数据类型由int型改成float型,那么效果又会如何呢?实际情况中我就有朋友这么做过,最后他非常惊奇的发现,对于一个热门
循环,做了如下的插装以后:- float entry_count, iteration_count, exit_count;
- ...
- entry_count=entry_count+1.0;
- while(...){
- iteration_count=iteration_count+1.0;
- ...
- }
- exit_count=exit_count+1.0;
复制代码 ;
那么我们可以期待,对于这个代码,循环平均迭代次数将会是值iteration_count/entry_count.
可是他们遇到了一种情况,虽然这个热门循环从语意上可以看出迭代次数应该在4到5次之间,可以每次运行统计出来的结果表示
iteration_count/entry_count都正好是1.0,大家说这会是什么原因呢? |
|