x86上的C 64位循环性能
对于一些使用原始套接字的IPv4 ICMP处理代码,我需要一个Internet校验和函数(一个补码校验和),我偶然发现了在64位Intel处理器(使用gcc 4.8.2)上无法解释的行为。我想知道是否有人能解释一下 我使用32位累加器实现了第一个校验和函数,并执行16位求和。然后,我使用64位累加器和32位求和实现了相同的功能,认为求和越少,执行速度越快。结果是第一个实现的运行速度是第二个实现的两倍(O3优化)。我就是不明白为什么 下面的代码实际上并没有执行精确的校验和(我简化了它),但说明了这个问题。两者都编译为64位,在64位本机平台上运行(LP64:short 16位,int 32位,long 64位,指针64位)x86上的C 64位循环性能,c,performance,algorithm,64-bit,C,Performance,Algorithm,64 Bit,对于一些使用原始套接字的IPv4 ICMP处理代码,我需要一个Internet校验和函数(一个补码校验和),我偶然发现了在64位Intel处理器(使用gcc 4.8.2)上无法解释的行为。我想知道是否有人能解释一下 我使用32位累加器实现了第一个校验和函数,并执行16位求和。然后,我使用64位累加器和32位求和实现了相同的功能,认为求和越少,执行速度越快。结果是第一个实现的运行速度是第二个实现的两倍(O3优化)。我就是不明白为什么 下面的代码实际上并没有执行精确的校验和(我简化了它),但说明了这
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;
}
我不喜欢这样的替身
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;
}
既然你写了:
两者都编译为64位,在64位本机平台上运行(LP64:short 16位,int 32位,long>64位,指针64位)
我建议使用(unsigned long*)。有些人建议在反汇编代码中检查实际情况。我想这是因为你的int*cast加了长累加器
没有O2O3标志怎么办?您能告诉我在正常编译模式下的速度是多少吗?我认为它无法展开“for”循环,因为从char*转换为unsigned int*。类型转换通常会阻止编译器优化代码,因为在这种情况下无法进行完美的别名分析。如果您首先声明一个附加的本地指针来在循环之前强制转换“数据”指针,这样循环中就没有任何强制转换,编译器应该能够优化“for”循环。可能的答案是:“i
我也更喜欢在循环之外进行类型转换,但这也揭示了一个限制-您的数据必须我以前也遇到过类似的问题;我在两个代码中都找不到任何问题。但对我有效的是改变编译器 我猜GCC正在编写不推荐的程序集 如果你可以反编译你的应用程序,我们可以对这个问题有更多的了解,但是这里没有足够的信息 当我反编译代码时,我发现它多次重写了整个方法。但那可能只适合我 希望这对你有所帮助,这里几乎没有这方面的信息
如果我不得不猜测我会同意Learner的观点,我非常确定反编译的代码会指向for循环。我对这个问题很感兴趣,所以请回复。您是否使编译器的工作变得困难。在内部循环中,您自己通过选择索引步长和强制转换来计算字节偏移量。这可能会阻止循环展开或任何其他试图假定对齐的优化。也可能不允许编译器使用寻址模式并计算有效地址本身(或LEA-it)
如果我这样做,我会将循环顶部的数据指针投射到您的步幅类型,并将循环计数器增加1。编译器可能会更高兴一点 您是否尝试过将其分解并查看它们各自变成了什么?我们是否可以假设原始缓冲区在两个示例中正确对齐了段落?您是否确保
数据
正确对齐?如果它是2字节对齐的,则int版本的性能将受到影响。我还建议使用固定宽度的类型,以避免任何关于类型大小的歧义,即uint32\u t
,uint64\u t
,等等。同时尝试让函数接受无符号int*
;也许在char*
版本中,编译器决定生成较少的最佳代码,因为它无法确定您是否传入了对齐指针。程序的整个要点是求32位值的和。将强制转换更改为读取64位值会产生错误的结果。那么这是正常的。正如其他人所评论的,这是一个填充问题。此外,如果ur整数只有32位大,则保存/添加两个64位整数将需要额外的操作/检查以获得正确的结果,尤其是当数字超过32位时。请“弃用”。不“贬值”。
uint64_t sum = 0;
const uint32_t* dptr = (const uint32_t*) data;
while (size > 3)
{
sum += (uint32_t) *dptr++;
size -= 4;
}
sum += *((unsigned int*) (data + i));