事情的起因在
上篇帖子最后有交代,这里就不细谈了,先贴下我的测试程序代码:
#include <stdio.h>
int get_step( unsigned long num )
{
/* ... */
}
int get2( unsigned long n )
{
/* ... */
}
int main( int argc, char* argv[] )
{
unsigned long num;
printf ("Use get_step()\n");
for (num = 1; num; num++) {
int step = get_step (num);
if (!(num & 0xFFFFFF)) {
printf ("%d: Now at %u:%d\n", (int)time (NULL), num, step);
}
}
printf ("Use get2()\n");
for (num = 1; num < 0xFFFFFFFF; num++) {
int step = get2 (num);
if (!(num & 0xFFFFFF)) {
printf ("%d: Now at %u:%d\n", (int)time (NULL), num, step);
}
}
return -1;
}
|
get_step()和get()的代码也在
上篇帖子里。在命令行下用"cl /Ox /TC t.c"编译后运行发现,get_step()比get()耗时要短很多。但从get_step()和get2()的算法本身看,应该不存在大的差异才对。于是反汇编看了一下代码,原因如下:
1) VC识别到,在for循环体内,当num & 0xFFFFFF不为0时,赋值语句int step = get_step (num)实际上没有任何意义,此时该step值不存在任何引用,故在经过编译优化后,第一个for循环体内的代码实际变为:
for (num = 1; num; num++) {
if (num & 0xFFFFFF) {
int step = get_step (num);
printf ("%d: Now at %u:%d\n", (int)time (NULL), num, step);
}
}
|
应该承认,对于性能而言,这是一个很棒的优化。当然,该优化也存在一些副作用,如著名的memset (sPassword, 0, strlen (sPassword))问题。事实上,对该优化(我称之为基于函数调用价值判断的优化)我事先也考虑到了,但更进一步,我考虑到对于get2()的测试,我使用的是完全相同的for循环形式,故我认为两者的优化应该不存在区别,不过,实际上呢?
2) 实际上是,VC在优化get2()时,发现该函数代码相当短小,故在所有调用点直接将其展开!这直接导致的后果就是,当get2()的代码被展开后,在第二个for循环中做优化时,当(num & 0xFFFFFF)不为0,仅仅去掉了对变量step的赋值,而get2()本身的代码依然会被执行!这就是我的测试中get2()代码性能比get_step()差太多的真正原因!而真正让我懊恼的是,事实上对该优化,事前我也想到了(!),正基于此,我在编译时使用了/TC参数,指定VC按C而非C++程序来编译。照我的之前理解,这样类似C++ inline的函数展开式优化是不应该应用到C这样相对底层的语言上的,但显然,MS基于其对性能追求的理解,牺牲了少部分C程序员绝对不肯妥协的对程序代码的控制力。实际上这也是一个平衡,应该说绝大多数情况下MS的选择(估计也是目前绝大多数编译器厂商的选择)颇为可取,但不幸的是在这里,这样的选择却是不幸的!
明白了问题所在就很容易处理了,关闭VC优化(cl /TC t.c),或使用尺寸优化而非性能优化(cl /O1 /TC t.c),或者直接禁止VC展开非inline函数(cl /Ob1 /Ox /TC t.c),或为for循环体内的每次step赋值都加上一个引用如下,即可:)
for (num = 1; num; num++) {
int step = get_step (num);
if (!step) {
printf ("Ha!\n");
} else if (!(num & 0xFFFFFF)) {
printf ("%d: Now at %u:%d\n", (int)time (NULL), num, step);
}
}
|
结论:
1)VC的优化顺序是否可以优化?如果编译时,能先做基于函数调用价值判断的优化,再做函数展开优化,显然产生的代码更高效。果真如此,我最初的代码虽然不能得到正确的结果,但也不会对两个原本效率接近的算法输出明显不合理的偏差;
2)有些问题虽然考虑到了,考虑的逻辑也没问题,但不表示和实际一致,因为别人的考虑不一定符合逻辑,至少不一定符合你的逻辑:)
3)这又是好多年不搞反汇编的结果,试想如果象n年前一样每周至少看半天时间以上的反汇编代码,这种问题应该是烂熟于心的了,根本不用这样临时抱佛脚。事实上,“考虑到了但没去检验”,就是对“检验过程”生疏的一种表现...
阅读(2851) | 评论(0) | 转发(0) |