校验和快速计算方法

先将代码贴上

 1 uint16_t cal_checksum(uint32_t *pstart,uint16_t len)
 2 {
 3     uint64_t checksum;
 4     uint32_t *pend;
 5     uint32_t v0,v1,v2,v3,v4;
 6      checksum = 0;
 7     pend = (uint32_t*)((char*)pstart + (len & (~0xF)));
 8     while(pstart < pend)
 9     {
10         v0 = *pstart;
11         v1 = *(pstart+1);
12         v2 = *(pstart+2);
13         v3 = *(pstart+3);
14 
15         checksum += v0;
16         checksum += v1;
17         checksum += v2;
18         checksum += v3;
19 
20         pstart += 4;
21     }
22 
23     len = len & 0xF;
24     pend = (uint32_t*)((char*)pstart + (len & (0xF)));
25     while(pstart < pend)
26     {
27         v0 = (uint32_t)(*(uint16_t*)pstart);
28         v1 = (uint32_t)(*((uint16_t*)pstart+1));
29 
30         pstart += 1;
31 
32         checksum += v0;
33         checksum += v1;  
34     }
35 
36     switch(len & 0x3)
37     {
38         case 3:
39             v0 = (uint32_t)(*(uint16_t*)pstart);
40             v1 = ((uint32_t)(*((uint8_t*)pstart+2)) << 8);
41             checksum += v0;
42             checksum += v1;
43             break;
44         case 2:
45             v0 = (uint32_t)(*(uint16_t*)pstart);
46             checksum += v0;
47             break;
48         case 1:
49             v0 = ((uint32_t)(*((uint8_t*)pstart+2)) << 8);
50             checksum += v0;
51         default:
52             break;
53     }
54 
55     checksum = (checksum>>32) + (checksum&0xFFFFFFFF);
56     checksum = (checksum>>32) + (checksum&0xFFFFFFFF);
57     checksum = (checksum>>16) + (checksum&0xFFFF);
58     checksum = (checksum>>16) + (checksum&0xFFFF);
59 
60     return checksum ^ 0xFFFF;
61 }
 

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

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

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

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

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

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

当然此代码也存在限制,那就是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首地址直接传进更正之后的函数,这是因为不管是Ethernet II还是802.3定义的MAC头都满足4n+2字节,甚至VLAN字段数据长度也是4的倍数。

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

最初接触第二条优化方式,是在看<高效程序的奥秘>时;查看Linux内核时,发现其checksum计算方法也是使用此技巧。

转载于:https://www.cnblogs.com/long-wu/p/4165293.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值