对于某些使用原始套接字的 IPv4 ICMP 处理代码,我需要一个 Internet 校验和函数(一个的补码校验和),但我偶然发现了我无法在 64 位 Intel 处理器(使用 gcc 4.8.2)上解释的行为。我想知道是否有人可以阐明它。
我使用 32 位累加器实现了第一个校验和函数并执行 16 位求和。然后我使用 64 位累加器和 32 位求和实现了相同的方法,认为更少的求和会导致更快的执行。结果是第一个实现的运行速度是第二个实现的两倍(使用 O3 优化)。我只是想不通为什么...
下面的代码实际上并没有执行准确的校验和(我已经简化了它)但说明了问题。两者都编译为 64 位,在 64 位 native 平台上运行(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 秒。
更新:
看来是硬件问题。运行内存诊断显示偶尔出现总线奇偶校验错误(不确定为什么这对 32 位版本的影响比对 16 位版本的影响更大,但你知道了)。代码在其他服务器上按预期运行。将在接下来的几个小时内删除该问题(与硬件相关,它不再特别有用)。
最后更新:
有趣的是,将 for
循环替换为 while
循环并使用 O3 优化进行编译(如下所示,针对 64 位累加器的情况)同时获得 32 位和 64 -bit 累加器案例以相同的速度运行。这是因为编译器执行了一些循环展开(奇怪的是,它没有展开 for
循环)并使用 mmx
寄存器执行求和...
uint64_t sum = 0;
const uint32_t* dptr = (const uint32_t*) data;
while (size > 3)
{
sum += (uint32_t) *dptr++;
size -= 4;
}
最佳答案
我之前遇到过类似的问题;我在我们的任何一个代码中都找不到任何问题。但对我有用的是更改编译器。
我的猜测是 GCC 正在编写已弃用的程序集。
如果您可以反编译您的应用程序,我们可以更清楚地了解这个问题,但这里没有足够的信息继续进行。
当我反编译我的代码时,我发现它多次重写了整个方法。但这可能只适合我。
希望这对您有所帮助,几乎没有任何关于此的信息。
如果我不得不猜测我会同意 Learner 的观点,我很确定反编译代码会指向 for 循环。我对这个问题很感兴趣,所以请回复。
关于x86 上的 C 64 位循环性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21654207/