先将代码贴上
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计算方法也是使用此技巧。