x86上的C 64位循环性能

我需要使用原始套接字的一些IPv4 ICMP处理代码的Internet Checksumfunction(补码校验和),并且我无法解释在64位英特尔处理器(使用gcc 4.8.2)上无法解释的行为。 我想知道是否有人可以说明一点。

我使用32位累加器实现了第一个校验和function,并执行了16位和。 然后我使用64位累加器和32位和来实现,认为总和越less,执行速度越快。 结果是第一个实现的运行速度是第二个的两倍(使用O3优化)。 我简直不知道为什么…

下面的代码实际上并没有执行准确的校验和(我简化了它),但是说明了这个问题。 两者都编译为在64位本机平台上运行的64位(LP64:短16位,整型32位,长64位,指针64位)。

  1. 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; } 

在相同的10k数据上进行50,000次函数调用:约1.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位版本,但你去)。 代码按预期在其他服务器上运行。 在接下来的几个小时内将会删除这个问题(与硬件有关,不再特别有用)。

最终更新:

有趣的是,使用while循环replacefor循环并使用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; } 

我之前有过类似的问题, 我在任何一个代码中都找不到任何问题。 但是什么对我来说是改变了编译器。

我的猜测是海湾合作委员会正在编写废弃的大会。

如果您可以反编译您的应用程序,我们可以在这个问题上发现更多的信息,但是这里没有足够的信息。

当我反编译我的代码,我发现它是重写整个方法多次。 但那可能只是为了我。

希望这对你有所帮助,这个地方几乎没有任何信息。

如果我不得不猜测,我会同意学习者,我很确定反编译的代码将指向for循环。 我对这个问题很感兴趣,所以请回复。

可能的答案:“i <size – 1”条件可以比“i <size – 3”更有效地编译和执行。 第一个只需要一个递减指令,而不是另一个需要一个常量3来加载的地方。 这个计算是每次迭代执行的。 你应该在别处存储这个计算的结果。

这与while循环无关。 当你重写while循环时,你也改变了迭代条件,并消除了上面的原因。

我也更喜欢在循环之外进行types转换,但是这也显示出一个限制 – 你的数据必须

你是否让编译器的工作困难? 在内部循环中,您可以通过select索引步长和演员来自行计算字节偏移量。 这可能会阻止循环展开或任何其他尝试假设alignment的优化。 也可能不会让编译器使用寻址模式并出去计算自己的有效地址(或LEA它)。

如果我这样做,我会把循环顶部的数据指针转换为您的步幅types,并将您的循环计数器增加1.编译器可能会更快乐一些。

我认为它不能展开“for”循环,因为从char *转换为unsigned int *。 types转换通常会阻止编译器优化代码,因为在这种情况下不能进行完美的别名分析。 如果你首先声明一个额外的本地指针来在循环之前强制转换你的“数据”指针,这样在循环中没有任何强制转换,编译器应该能够优化“for”循环。

 sum += *((unsigned int*) (data + i)); 

我不喜欢这样的演员

64位累加器和32位和

既然你写道:

两者都编译为在64位本机平台上运行的64位(LP64:短16位,整型32位,长整型,64位,指针64位)。

我会build议使用(无符号长*)。 有人build议检查反汇编代码实际上是怎么回事。 我认为这是因为你的int *与长累加器投。

如果没有O2 <> O3标志呢? 你能展示一下正常编译模式下的速度吗?