【发布时间】:2014-03-06 10:14:24
【问题描述】:
对于一些使用原始套接字的 IPv4 ICMP 处理代码,我需要一个 Internet 校验和函数(一个补码校验和),但我偶然发现了在 64 位英特尔处理器(使用 gcc 4.8.2)上无法解释的行为。我想知道是否有人可以对此有所了解。
我使用 32 位累加器实现了第一个校验和函数并执行 16 位求和。然后我使用 64 位累加器和 32 位求和实现了相同的操作,认为更少的求和会导致更快的执行。结果是第一个实现的运行速度是第二个的两倍(使用 O3 优化)。我就是不知道为什么……
下面的代码实际上并没有执行准确的校验和(我已经简化了它),但说明了问题。两者都编译为 64 位,在 64 位本机平台上运行(LP64:short 16 位,int 32 位,long 64 位,指针 64 位)。
-
32 位累加器和 16 位求和
unsigned short cksum_16_le(unsigned char* data, size_t size) { unsigned short word; unsigned int sum = 0; unsigned int i; for(i = 0; i < size - 1; i += 2) sum += *((unsigned short*) (data + i)); sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return ~sum; }
50,000 次函数调用对相同的 10k 数据:~1.1 秒。
-
64 位累加器和 32 位求和
unsigned short cksum_32_le(unsigned char* data, size_t size) { unsigned long word; unsigned long sum = 0; unsigned int i; for(i = 0; i < size - 3; i += 4) sum += *((unsigned int*) (data + i)); sum = (sum & 0xffffffff) + (sum >> 32); sum = (sum & 0xffffffff) + (sum >> 32); sum = (sum & 0xffff) + (sum >> 16); sum = (sum & 0xffff) + (sum >> 16); return ~sum; }
50,000 次函数调用对相同的 10k 数据:~2.2 秒。
更新:
问题似乎是由硬件引起的。运行内存诊断显示偶尔的总线奇偶校验错误(不知道为什么这会比 16 位版本更影响 32 位版本,但你去了)。代码在其他服务器上按预期运行。将在接下来的几个小时内删除问题(与硬件相关,它不再特别有用)。
最终更新:
有趣的是,将 for 循环替换为 while 循环并使用 O3 优化进行编译(如下所示的 64 位累加器案例)可使 32 位和 64 位累加器案例以相同的速度运行。这是因为编译器执行了一些循环展开(奇怪的是,它不展开 for 循环)并使用 mmx 寄存器执行求和...
uint64_t sum = 0;
const uint32_t* dptr = (const uint32_t*) data;
while (size > 3)
{
sum += (uint32_t) *dptr++;
size -= 4;
}
【问题讨论】:
-
你有没有试过拆开它,看看它们各自变成了什么?
-
我们应该假设原始缓冲区在 both 示例中的段落对齐正确吗?
-
您确定
data正确对齐了吗?如果它是 2 字节对齐的,那么 int 版本的性能会受到影响 -
我还建议使用固定宽度的类型来避免任何关于类型大小的歧义,即
uint32_t、uint64_t等。 -
也尝试让你的函数接受
unsigned int *;也许在char *版本中,编译器决定生成不太优化的代码,因为它无法确定您是否传递了对齐的指针
标签: c performance algorithm 64-bit