您的位置:首页 > 其它

校验和快速计算方法

2014-12-15 17:16 176 查看
先将代码贴上

uint16_tcal_checksum(uint32_t*pstart,uint16_tlen)
{
uint64_tchecksum;
uint32_t*pend;
uint32_tv0,v1,v2,v3,v4;
    checksum=0;
pend=(uint32_t*)((char*)pstart+(len&(~0xF)));
while(pstart<pend)
{
v0=*pstart;
v1=*(pstart+1);
v2=*(pstart+2);
v3=*(pstart+3);

checksum+=v0;
checksum+=v1;
checksum+=v2;
checksum+=v3;

pstart+=4;
}

len=len&0xF;
pend=(uint32_t*)((char*)pstart+(len&(0xF)));
while(pstart<pend)
{
v0=(uint32_t)(*(uint16_t*)pstart);
v1=(uint32_t)(*((uint16_t*)pstart+1));

pstart+=1;

checksum+=v0;
checksum+=v1;
}

switch(len&0x3)
{
case3:
v0=(uint32_t)(*(uint16_t*)pstart);
v1=((uint32_t)(*((uint8_t*)pstart+2))<<8);
checksum+=v0;
checksum+=v1;
break;
case2:
v0=(uint32_t)(*(uint16_t*)pstart);
checksum+=v0;
break;
case1:
v0=((uint32_t)(*((uint8_t*)pstart+2))<<8);
checksum+=v0;
default:
break;
}

checksum=(checksum>>32)+(checksum&0xFFFFFFFF);
checksum=(checksum>>32)+(checksum&0xFFFFFFFF);
checksum=(checksum>>16)+(checksum&0xFFFF);
checksum=(checksum>>16)+(checksum&0xFFFF);

returnchecksum^0xFFFF;
}




RFC规定的checksum的计算方法是对每两个字节当做一个数进行计算,出现进位则加到低位上。

此处的代码优化有两个点:

1  交叉使用变量,以便节省装载延迟导致CPU等待

2  一次加法完成两对16字节数据相加,低16位进位则先加到高16位上,最终高16位也会加回低16位;为了防止高16位溢出,使用了uint64_t类型以便记录溢出

优化的第二条借鉴自Cavium的代码,不过可惜的是其代码由于使用非对齐加载以及未使用第一条优化,并且判断条件过多等,导致其性能严重低下。

优化后的代码性能基本上达到,cycle_num=len/2(不考虑cachemiss),也就是说一个1500的数据包大概只需要不到800个cycles就能够完成checksum计算(CPU1GHZ);

当然此代码也存在限制,那就是pstart指针至少需要4字节对齐,这就是为什么将其类型写成uint32_t*的原因。若不对齐,轻者严重影响效率;重者CPU出错。

一般来说,buff的首地址至少4字节对齐,计算checksum时,数据应该都已经在buff中装配好了,假设说buff中的开始数据是MAC头,接着是IP头,再跟着TCP头,那么只需要将

checksum=0

替换成

checksum=*(uint16_t*)pstart;

pstart=(uint32_t*)((uint16_t*)pstart+1);

len-=2;

即可直接将IP的payload首地址直接传进更正之后的函数,这是因为不管是EthernetII还是802.3定义的MAC头都满足4n+2字节,甚至VLAN字段数据长度也是4的倍数。

在含有PREFETCH指令的系统,值得将一次处理16个字节改成32个字节,以便加入PREFETCH指令而不会产生比较大的性能损失。

最初接触第二条优化方式,是在看<高效程序的奥秘>时;查看Linux内核时,发现其checksum计算方法也是使用此技巧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: