Chinaunix首页 | 论坛 | 博客
  • 博客访问: 792298
  • 博文数量: 155
  • 博客积分: 4056
  • 博客等级: 上校
  • 技术积分: 1531
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-04 14:46
文章分类

全部博文(155)

文章存档

2011年(4)

2010年(4)

2009年(44)

2008年(36)

2007年(34)

2006年(28)

2005年(5)

我的朋友

分类: C/C++

2008-09-05 23:55:26

事情的起因在上篇帖子最后有交代,这里就不细谈了,先贴下我的测试程序代码:

#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) |
给主人留下些什么吧!~~