代码高效性和健壮性的权衡

代码高效性和健壮性的权衡

http://www.cnblogs.com/walzer/archive/2009/12/14/1623945.html

Posted on 2009-12-14 17:29 Walzer 阅读(428) 评论(0) 编辑 收藏

这个是比较早, 09年4月份的事情了。整理文档翻出来,觉得还有点意思.

 

当时CLIENT-SERVER的通讯封包格式有两种方案

a. 以7E为开头和结尾, PAYLOAD中所有7E的字节, 都在其后扩展一个BYTE, 写为7E, 7D, (称为转义). 封包中不带CHECKSUM, CRC等校验用的字段

b. 以7E为开头和结尾, 带一个CHECKSUM字段, PAYLOAD中不进行7E->7E 7D的转义.

几个同事就这个通信封包格式, 采用方案一或方案二, 开会激烈讨论了个把小时。
我原先反对转义方案的出发点比较模糊, 只是觉得原先转义的方案"不优雅"; 后来才想清楚了不优雅的"本质"在哪里.

 

所有的代码, 可以在抽象意义上分作两大块, 两者的着重点是不同的.

(1) 正常运行的代码. 首要追求高效性,
    这个"高效性"如果从逻辑的角度来解释, 那么一方面是"高效"地对正确的数据执行正确的算法(方法/策略), 另一方面是"高效"地找出异常, 然后丢给异常处理代码去处理.

(2) 处理异常的代码. 首要追求健壮性. 
    就是程序必须能从异常中自我恢复. 由于代码多数时间跑的是"正常"逻辑, 少数情况下才不得不处理"异常", 所以"异常"处理的代码中, 首要任务是健壮, 跑不死, 而高效性则是次要的.

 

那么回到转义的策略上来看,原先的7E -> 7E 7D, 使得装包和拆包的时候, 时间上都必须挨字节扫描过去, 空间上必须另开一块内存, 这些"不优雅"的工作是为了应对网络传输时包数据丢失. 包数据丢失是一个异常情况,而转义策略本质上就是不论好包坏包,一棍子打死, 统统要经过转义算法. 用上面的观点解释, 即"为了异常情况下的健壮性,牺牲了正常情况下的高效性".

而用Header + Length + CheckSum + Payload + Tailer的做法, 逻辑上是这样的

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->if ( 以Length为基础, 得知CheckSum正确 和 Tailer正确)
{
    正确的包,走正常处理流程, 直接把Payload传给上层逻辑处理
} 
else
{
    错误的包,走异常处理流程,挨字节扫描下一个Header, 然后再算length, checksum, tailer等
}


 

这是在上面"正常->高效性 & 异常->健壮性"指导思想下的做法. 那么现在就剩最后一个问题, 计算checksum和转义的工作相比, 哪一个更快? 如果转义处理的效率, 比checksum更高,那么上面的假设就不成立了.

所以我做了个实验, 代码如下

代码 


Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->BYTE* pBuf1 = new BYTE[1024];
BYTE* pBuf2 = new BYTE[1024*2];
UINT8 sum = 0;
DWORD tStart = 0, tEnd = 0;

// CODE 1, 转义处理
tStart = GetTickCount();  // WM上的毫秒级时间

for(int j = 0; j < 100000; j++)
{
    for(int i1 = 0, i2 = 0; i1 < 1024; i1++, i2++)
    {
        if (pBuf1[i1] == 0x7E && pBuf1[i1+1] == 0x7D)
        {
            pBuf2[i2] = pBuf1[i1];
            i1++;            
        } 
        else
        {
            pBuf2[i2] = pBuf1[i1];
        }
    }

tEnd = GetTickCount();
        
printf("copy 1024 bytes * 100K times, use %d ms\n", tEnd - tStart);

// CODE 2, CHECK SUM
tStart = GetTickCount();

for(int j = 0; j< 100000; j++)
{
    for(int i = 0; i < 1024; i++)
    {
        sum += pBuf1[i];
    }
}

tEnd = GetTickCount();

printf("check sum 1024 bytes *100K times,  use %d ms\n", tEnd - tStart);


 

上面这段代码,在SAMSUNG 2442 400MHz的CPU, WM 6.1系统上运行结果是

copy 1024 bytes * 100K times, use 11677 ms
check sum 1024 bytes *100K times,  use 7504 ms

所以, 一个正确的数据包, 经过CHECKSUM计算的时间, 比其经过转义计算的时间要快得多, 仅为其64%.  这是手机上的情况, 服务器上的百分比不太清楚是什么样,但至少有一点是肯定的,就是用CHECKSUM的方案比用转义的方案,在正常逻辑情况下速度更快、内存开销更少。当服务器同时处理十万数量级网络数据包的时候, 性能提升还是比较可观的。

 

这篇文章的重点不在于哪个方案更严谨,或者上面的逻辑对不对,而是在于这么一个思想:

(1) 正常运行的代码. 首要追求高效性,

(2) 处理异常的代码. 首要追求健壮性. 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值