世间本无事,佣人自扰之

新装了空调

06月 30th, 2005 by solarsea

    实验室新买的空调今天终于装上了,因为要调整线路,大家都把电脑关了,闲聊了一下午。结果学校管电的人来看了以后说,没带够工具,要明天才能改。晕,这算什么工作态度啊,只敢在学生面前抖抖。

    空调装好以后,用了个接线板临时连了一下,现在两个空调对着吹,感觉凉快多了,hoho。

Posted in 信手涂鸦 | No Comments

发现一个优化速度的方法

06月 29th, 2005 by solarsea

    之前接收任务和解码任务采用的是不同的优先级,因为解码花费的时间多,所以把解码任务的优先级设为高,发现接收缓存中无数据时再切换到接收任务。这其实相当于单任务,收一个包再解一个包,之所以这么做主要是因为两点,一是效率,频繁任务切换还是要耗费一定系统资源的,二是psos中不能使用对象的锁定,只能靠简单信号量同步,对公共数据的访问可能会有冲突。但这么一来就有一个致命的问题,就是如果用pc发送已编码的数据,不能发送得太快,否则接收端可能因为正在执行解码任务而没有接收到新数据,这样解码输出时常会停顿一下。造成这个现象的原因是无法从已编好的文件分出每帧的数据,因此每次发出的数据很可能不包含整数帧,解码器要是没有获得该帧剩下的数据,则可能无法继续解码。

    下午发现,如果把两个任务设为相同的优先级,让系统自己进行时间分片,则能大大改善停顿现象,因为任务之间还通过信号量进行同步,于是不会发生数据冲突,但是却改善了丢包现象,现在可以用原来1.5倍的速率发送数据。

    如果不用信号量进行同步的话,考虑了一下,接收需要采用链表,这里也有一个效率的问题,因为要频繁分配释放内存,dsp毕竟性能不如pc,主频才100M,不过也可以试一下,看看哪种效果好。

Posted in 校园拾贝, 毕业进行时 | No Comments

忙碌一天

06月 28th, 2005 by solarsea

    突然听说要把板子拿到某公司去演示,听到这个消息,就知道又要加班了。早上一来发现硬件台上的机子已经打开了,就有一种不好的预感,果然,李老师过来说了一下,也就没心情悠闲看新闻了。先和李老师一起解决了色度信号采样的问题,另外总结了一下,感觉目前编解码不匹配的原因是编码器发出的数据不太正常,但是还没有什么好的解决办法。然后测试了一下解码器,没什么问题,就是觉得速度太慢。于是李老师就问原来优化过的版本还有吗,ft,前两天整理备份的时候,觉得没什么用,刚删了。只好再移植一次了,不过这次倒是很顺利,本来预计要到明天才能搞定,结果下午就ok了,而且没什么问题,能顺利解码事先编好的文件,看来以前出的问题,原因都在编码后传输这一块了。测试了几个视频源文件,都没问题,先把这次演示应付过去再说吧。

Posted in 校园拾贝, 毕业进行时 | No Comments

程序设计语言综合症

06月 25th, 2005 by solarsea

同样来自《Write Clean Code》。

程序设计语言综合症

那些不知道C语言代码是如何转换为机器代码的程序员,经常试图通过使用简炼的C语句来提高机器代码的质量。他们认为,如果使用最少量的C语句,那么就应该得到最少量的机器代码。在C代码数量和相应的机器代码数量之间存在着一定的关系,但当把这种关系应用到单行代码时,这种关系便不适用了。

你还记得第6章的uCycleCheckBox函数吗?

unsigned uCycleCheckBox(unsigned uCur)

{

return ((uCur<=1)?(uCur?0:1) : (uCur == 4)?2 : (uCur +1));

}

uCycleCheckBox可以说是简炼C代码,但是正如我指出的那样,它产生了很糟的机器代码。再看上一节中给出的返回语句:

return((int)atou(str + (*str == ‘+’)));

如果你使用的是优化得很好的编译程序,并且你的目标机不用任何分支指令即可生成0/1的结果,那么把比较的结果加到指针上,这条语句将产生相当好的代码。如果不具备上面描述的条件,编译程序很可能要作比较在内部将其扩展为 ?:操作,生成的代码就好象你写了如下所示的C代码一样:

return ((int)atou(str+((*str == ‘+’)?1:0)));

由于“?:”操作只不过是化了妆的if-else语句,因此所得到的代码可能比下面明显、直观的代码更坏:

if(*str == ‘+’)   /* 跳过可选的‘+’号 */

    str++;

return ((int)atou(str));

当然还有其它的方法来优化上面的代码。我曾经见到这样一些情况:程序员将一个两行的if语句用“||”操作符“改进”成一行:

    if( (*sdtr != ‘+’) || str++ )     /* 跳过可选择的’+’号 */

        return ((int)atou(str));

这样的代码之所以可以工作是因为C语言具有短路求值规则,但是将代码放在一行上并不能保证比使用if语句产生更好的机器代码;如果编译程序产生的0或1有副作用的话,使用“||”甚至会得到更坏的代码。

需要记住这个简单规则:把“||”用于逻辑表达式,把“?: ”用于条件表达式,把if用于条件语句。这并不是说把它们混合起来就好,而往往出于使代码高效和可维护。

我的观点是:如果你总是使用稀奇古怪的表达式,以便把C代码尽量写在源代码的一行上,从而达到最好的瑜伽状态的话,你很可能患有可怕的“一行清”疾病(也称为程序设计语言综合症)。那么你就要做个深呼吸,反复地提醒自己:“多行源代码可能产生效率高的机器代码。多行源代码可能产生效率高的机器代码……。”

紧凑的C代码并不能保证得到高效的机器代码

切勿傲慢轻率

世上最令人厌烦的书,就是那些由专家撰写的、其内容充满了没有必要的技术术语的书。他们不说“该错误可能使你的系统暂停或失败”,而是说“这样的程序设计缺陷可能导致丧失对系统的控制或引起系统终止”。他们还用象“公理化程序验证”和“缺陷分类”这样的术语,好象程序员每天都要用到些术语似的。啧!啧!啧!啧!这些作者将他们要说明的信息隐藏在含糊难懂的术语中,这不仅没有帮助读者,反而使读者更糊涂了。不只是书作者这么做,有一些程序员也热衷于编写含糊难懂的代码,他们认为只有代码含糊不清才能给人留下深刻的印象。

例如,看看下面的函数是怎样工作的:

void *memmove(void *pvTo,void *pv From,size_t size)

{

    byte *pbTo = (byte *)pvTo;

byte *pbFrom = (byte *)pvFrom;

((pbTo < pbFrom)?(tailmove:headmove)(pbTo,pbFrom,size)

    return (pvTo);

}

如果我将它改写如下,该函数是否更好理够呢?

void *memmove(void *pvTo,void *pvFrom,size_t size)

{

byte *pbTo = (byte *)pvTo;

byte *pbFrom = (byte *) pvFrom;

if(pvTo < pbFrom)

tailmove(pbTo,pbFrom,size);

else

headmove(pbTo,pbFrom,size);

return (pbTo);

}

第一个例子看起来不象合法的C语言程序,但实际上是。比较一下是很有好处的,第一个例子编译以后产生的代码比第二个例子所产生的代码要少得多。尽管如此,有多少程序员能理解第一个函数是怎样工作呢?如果他们必须维护该代码那又将如何呢?如果你写了正确的代码,但是没有人能够理解,那又有什么意义呢?如果不打算让别人看懂,你甚至可以用手工优化的汇编语言来编写这个函数。

下面的代码是使许多程序员费解的另一个例子:

while(expression)

{

    int i = 33;

    char str[20];

    … 其他代码 …

}  

请迅速回答,是每一次循环都要初始化i,还是仅仅第一次进入循环时对i进行初始化呢?你能不用思考就知道正确答案吗?如果你不能肯定,说明你训练有索,因为即使是专家级C程序员通常也要在脑子里浏览一下C语言的规则才能回答这个问题。

如果稍微修改一下,成为如下所示的代码。

While(expression)

{

int i;

char str[20];

i = 33;

… 其它代码 …

}

你对每次通过循环都要将i置为33还有什么疑问吗?在你的小组中还有程序员对此表示怀疑吗?当然没有。

和小说作家不一样,他们只有一类读者,而程序员却有两类读者:使用代码的用户和必须对代码进行更新的程序维护人员。程序员经常忘记这一点。我知道,忘掉用户的程序员并不多,但是根据我这些年读的程序来推测,程序员似乎忘记了他们的第二类读者:程序维护人员。

应该编写可维护的代码这一观点并不新奇,程序员知道应该编写这样的代码。可是,他们总是没有认识到,他们虽然整天编写可维护的代码,但是如果他们使用只有C语言专业人员才能理解的语言,那么这些代码实际上是不可维护的。根据定义,可维护的代码应该是维护人员可以很容易地理解并且在修改时不会引入错误的代码。不管怎样,程序维护人员一般都是该项目的新手而不是专家。

因此,当你考虑你的读者时,一定还要考虑到程序维护人员。下一次当你又想写下面的代码时:

strncpy(strDay,&“SunMonTueWedThuFriSat”[day*3],3);

你可以制止自己,并且以一种不让读者吃惊又很好理解的方式编写代码:

static char strDayNames[]=”SunMonTueWedThuFriSat”;

strncpy(strDay,&strdayNames[day*3],3);

为一般水平的程序员编写代码

Posted in 他山之石, 学海无涯 | No Comments

避免使用有风险的语言惯用语

06月 23rd, 2005 by solarsea

节选自 《Write Clean Code》,很多地方看了以后很有感触,但是觉得其中提到的某些要点,日常似乎也不太好应用上,贴一节自己觉得经典的吧。

下面给出一种实现 memchr函数的代码:

void* memchr( void *pv, unsigned char ch, size_t size )

{

    unsigned char *pch = (unsigned char * )pv;

    unsigned char *pchEnd = pch + size;

    while( pch<pchEnd && *pch != ch )

        pch ++;

    return( ( pch < pchEnd ) ? pch : NULL );

}

再与下面的memchr版本做一下比较:

    void* memchr( void *pv, unsigned char ch, size_t size )

    {

        unsigned char *pch = ( unsigned char * )pv;

        unsigned char *pchEnd = pch + size;

        while( pch < pchEnd )

        {

            if( *pch == ch )

                return ( pch );

            pch ++ ;

        }

        return( NULL );

    }

哪个看上去更好一些呢?第一个版本要比较pch和pchEnd两次,第二个版本只比较一次。哪个更清楚呢?更重要的是哪个第一次执行就可能正确呢?

由于第二个版本只在while条件中进行块范围检查,所以它更容易理解并且准确地实现了函数的功能。第一个版本的唯一长处是当需要将程序打印出来时,可以节省一些纸。

上面给出的memchr两个版本正确吗?你是否看出这两个版本具有同样一个细小错误?提示一下:当pv指向存储区的最后72个字节,并且size也是72时,memchr将要查找存储区的什么范围呢?如果答案是“存储区的全部范围,反复不断地查找。”那么你的回答就是对的。由于在程序中使用了有风险的惯用语,memchr陷入了无限的循环之中。

有风险的惯用语就是这样一些短语或表达式,它们看上去似乎能够正确地工作,但实际上在某些特殊场合下,它们并不能正确执行。C语言就是具有这样一些惯用语的语言,最好的办法是:无论什么时候,只要有可能就尽量避免使用这些惯用语。在memchr中有风险的惯用语是:

pchEnd = pch + size;

while( pch < pchEnd )

其中,pchEnd指向存储区中被查找的最后一个字符的下一个位置,因此它可用在while表达式中。程序员觉得这样使用很方便,如果所指的存储位置存在的话,则该程序会工作得很好,但是如果恰好查找到存储器的结尾处,那么所指的位置就不存在了(一个例外情况是:如果使用 ANSI C,总可以计算出某个数组之外的第一个元素的地址。ANSI C支持这个性能)。

作为改正错误的第一步尝试,将上面的代码改写为如下所示:

pchEnd = pch + size ? 1;

while ( pch <= pchEnd )

但是,这还不能正确工作。还记得前面讲过的BuildToLowerTable中UCHAR_MAX上溢错误吗?这里也有同样的错误。现在pchEnd可能指向一个合法的存储位置,但是,由于每一次pch增加到pchEnd + l时都要上溢,因此循环将不会终止。

当你可用指针也可用计数器时,使用计数器作为控制表达式是覆盖一个范围的安全方法:

void *memchr( void *pv, unsigned char ch, size_t size )

{

    unsigned char *pch = ( unsigned char * )pv;

    while( size — > 0 )

    {

        if( *pch == ch )

            return( pch );

        pch ++;

    }

    return( NULL );

}

上面的代码不仅正确,而且所产生的代码可能比前面各个版本产生的代码都要好,因为它不必初始化pchEnd。由于 size在减1之前必须先复制,以便与 0进行比较,所以人们通常认为size–版本比pchEnd版本要大一些并且要慢一些。可是,实际上对于许多编译程序来说,size–版本恰巧更快些、小些。这取决于编译程序是如何使用寄存器的。对于80×86编译程序言,还要取决于所使用的是哪种存储模型。不管怎样,在大小和速度方面,size–版本与pchEnd版本的差别很小,并不值得引起注意。

下面给出另一个惯用语,实际上在前面已经提过了。有些程序员可能会极力主张重写循环表达式,用–size代替size–:

while( –size >= 0 )

……

这种变化的合理一面是:上面这种表达式不产生比以前的表达式更坏的代码。在某些情况下,可能会产生稍好一点的代码。但它的唯一问题是:如果盲目奉行的话,错误将会象苍蝇见到牲畜一样向代码突袭而来。

为什么呢?

如果 size是无符号值(象memchr中的一样),根据定义,将总是大于或等于0,循环将永运执行下去,因此表达式不能正常工作。

如果size是有符号数,表达式也不能正常工作。如size是int类型并且以最小的负值INT_MIN进入循环,它先被减1,那么就会产生下溢,使得循环执行大量的次数。

相反,无论怎样声明size都能使“size– > 0”正确工作。这是个小小的、但又很重要的差别。

程序员使用“– size > 0”的唯一原因是想加快速度。让我们仔细看一下,如果真的存在速度问题,那么进行这种改进就好象用指甲刀剪草坪一样,可以这么做,但没有什么效果。如果不存在速度问题,那为什么又要冒这样的风险呢?这就好象没有必要让草坪的所有草叶都一样长,没有必要让每行代码效率都最优一样,要认识到最重要的是总体效果。

在某些程序员看来,放弃任何可能获得效率的机会似乎近似于犯罪。但是,当读完本书以后,你会得到这样的思想即使效率可能会稍稍低一点,也要使用安全的设计和实现来系统地减少风险性。用户不应该注意是否在某个地方又增加了一些循环,而应集中注意力来看是否在试图节省某些循环时而偶然引入了错误。用投资方面的一句术语来说就是:赢利并不能证明冒险是正确的。

使用移位操作来实现乘、除、求模2的幂是另外一种有风险的惯用语。它属于“浪费效率”类。例如,第2章给出的memset快速版本有如下几行代码:

pb = ( byte * )longfill(( long * )pb, 1, size/4 );

size = size % 4

可以肯定,有一些程序员在读到上面的代码时会想:“效率多低”。他们可能会将除操作和求模操作写成如下的形式:

pb = ( byte * )longfill(( long * )pb, 1, size >> 2 );

size = size & 3

移位操作比除法或求模要快,这在大多数机器上是对的。但是,象用2的幂去除或去求模无符号值(如size)的这样的操作,已经优化好了,即使商用计算机也是如此,没有必要再手工优化这些无符号表达式。

那么,有符号表达式又将怎样呢?显式的优化是否值得呢?既值得也不值得。

假定有如下的有符号表达式:

midpoint = ( upper + lower ) / 2;

当有符号数的值为负值时,将其移位与进行有符号除法所得的结果不同,因此二进制补码的编译程序不将除法优化为移位。如果我们知道上面表达式中的upper + lower总是正值,就可以采用移位将表达式改写成如下所示,这个代码要好一些:

midpoint = ( unsigned )( upper + lower ) >> 1

因此,优化有符号表达式是值得的。可是,移位是否是最好的方法呢?不是。下面代码所示的强制转换方法同样也很好,并且比移位法要安全得多。请在编译程序上试一下:

midpoint = ( unsigned )( upper + lower )/2;

上面的代码不是告诉编译程序要做什么,而是将需要进行优化的信息传递给编译程序。通过告诉编译程序所求得的结果是无符号的,来调知它可以进行移位。现在来比较一下两种优化,那个更容易理解?那个更具有可移植性?那个更可能在第一次执行就正确呢?

多年来,我发现了许多由于程序员使用移位来进行有符号值的除法,而有符号值又不能确保为正值而引起的错误;发现了许多方向移错了的移位错误;发现了许多移位位数错了的移位错误;甚至发现了由于不小心将表达式“a=b+c/4”转换为“a=b+c>>2”而引入的优先级错。但我却不曾发现过以键入’/’和’4’来实现除以4时会发生错误。

C语言还有许多其它的有风险的惯用语。有个最好的方法来找到自己经常使用的有风险的惯用语,这就是检查以前出现的每一个错误,再问一下自己:“怎样来避免这些错误?”然后建立个人的风险惯用语表从而避免使用这些惯用语。

Posted in 他山之石, 学海无涯 | No Comments

So Hot

06月 22nd, 2005 by solarsea

    今天BJ最高气温已经达到了39度,实验室里人多,机器也多,即便把空调开到最大,still very hot。今天开始着手处理启动的问题,先按照自己的思路用万用表查了EEPROM和JTAG周围的电路,觉得应该没有虚焊,然后用示波器检查波形,发现新板的波形不如旧板稳定,因为时好时坏,不太好确定,在你认为它波形不对时,偏偏就跳变一下然后就好了,实在不好确定。空载试了一下,好像又差不多,不知道这种情况怎么处理,新板的IC座比较紧,EEPROM不太好插进去,但是感觉也不像是这里的问题,只好去找李老师咨询一下。

    李老师说以前调板的时候,也有这个现象,当时最后得出的结论是,VO部分会对IIC总线产生影响,只要VO初始化出问题,JTAG就下载不了,所以后来改了电路,由TM1300给VIVO提供时钟。他觉得可能还是VO部分电路的问题,但是新板电路和去年底做的旧板电路是完全一样的啊。割断了VO的时钟线,发现可以下载了,为什么旧板VO的时钟线连着却没有问题呢。当时调试的时候也割断过,不行又连上了,可能是这么一断一连,略微影响了一点儿时序吧,不确定。李老师让先看看能不能运行原来的程序,不行的话,只能再连上找原因了。

    FT,这么热的天,再在屋里用烙铁,会被大家b4的,还是先别焊上吧。这么一来,VO部分就不能使用了,把代码download试了一下,发现ping不通网卡,代码应该没什么问题,于是单步跟了一下,发现原来是系统初始化时调用了VIVOInit()函数,现在没有vo了,psos就阻塞在这个函数里了。

    下午修改了视频驱动,把和vo有关的代码都注释了,再对源代码做了同样的改动,编码器不需要vo,这样,就只能把编码器放在新板上了。编译链接了一下,似乎没什么大问题了。但是debug版好使,release版不好使,没来得及找原因,凉快一会儿先,嘿嘿。

Posted in 校园拾贝, 毕业进行时 | No Comments

整理工具以及资料

06月 21st, 2005 by solarsea

    下午把解码器的代码用PolyStyle批量处理了一下,看着清爽多了。生成了release版本,测试了一下,没发现什么问题。剩下的时间,整理了一下硬件试验台,为了最近一段的调试,台子上多了烙铁,hub,电源,示波器等,尤其烦人的是网线,因为3根都很长,在台子上绕来绕去的,经常和两块板子的电源以及视频输入输出信号线缠在一起。之前因为大多数时间都用在调试上了,还总出不了结果,也就没有心情管这些。下午腾出了一些时间,把网线剪短了,重新上了水晶头,再把台子擦了一下,感觉舒坦多了,想想好像最近两个多月都没做清洁工作了,晕,这样都能忍过来,原来自己已经变得这么邋遢的说,sigh。

    写了周报,提交的时候发现自己在服务器上的资料文件夹也挺乱的,这学期开始服务器由师弟接管了,虽然有时也指导一下他怎样分类文档,但毕竟不是自己的责任了。最近一阵资料文件夹里积累了不少李老师给的资料,以及别的同学用于交流的文档,于是整理了一下,删去没用的,分了一下类,把自己的文档都整理了一遍。其间看到了开学时李老师给的备忘录,以及开题报告中计划完成的任务和时间,进度上问题不大,离完成似乎也不远了,原来,自己就要毕业了呀。

Posted in 校园拾贝, 毕业进行时 | No Comments

« Previous Entries

登录 | 访问数284866 | 水木BLOG | 水木社区 | 关于我们 | Blog论坛 | 法律声明 | 隐私权保护 | 京ICP证050249号
水木社区Blog系统是基于KBS系统WordPress MU架构的