今天,我注意到在我的64位PC上,int
,unsigned
,long long
和unsigned long long
之间的几个简单的按位和算术运算的速度差别很大。
特别是,以下循环的unsigned
速度约为long long
的两倍,这是我没想到的。
int k = 15;
int N = 30;
int mask = (1 << k) - 1;
while (!(mask & 1 << N)) {
int lo = mask & ~(mask - 1);
int lz = (mask + lo) & ~mask;
mask |= lz;
mask &= ~(lz - 1);
mask |= (lz / lo / 2) - 1;
}
(完整代码here)
以下是时间(以秒为单位)(适用于g++ -O
,-O2
和-O3
):
1.834207723 (int)
3.054731598 (long long)
1.584846237 (unsigned)
2.201142018 (unsigned long long)
这些时间非常一致(即1%的保证金)。
如果没有-O
标记,每个标记的速度会慢一秒,但相对速度是相同的。
这有明确的原因吗?
矢量化可能适用于32位类型,但我无法看到巨大的位置
long long
和unsigned long long
之间的差异来自。
某些类型的某些操作比其他类型的操作慢得多,
或者只是一般情况下64位类型较慢(即使在64位架构上)?
对于那些感兴趣的人,这个循环遍历{1,2,...,30}
的所有子集,恰好有15个元素。这是通过在小于1<<30
的所有整数上循环(按顺序)完成的,只需设置15位。
对于当前的情况,那是155117520次迭代。
我不知道这个代码段的来源了,但是this帖子解释了更多内容。
从汇编代码看,当类型无符号时,可以使除法更快。我认为这是有道理的,因为我们不必考虑符号位。
此外,32位操作使用movl
和其他xxxl
指令,
而64位操作使用movq
和xxxq
。
在阅读我链接的帖子后,我决定使用那里给出的公式:
T k = 15;
T N = 30;
T mask = (1 << k) - 1;
while (!(mask & 1 << N)) {
T t = mask | (mask - 1);
mask = (t + 1) | (((~t & -~t) - 1) >> (__builtin_ctz(mask) + 1));
}
这在上面发布的代码的大约三分之一的时间内运行,并且对所有四种类型使用相同的时间。
答案 0 :(得分:8)
代码中最慢的操作是
mask |= (lz / lo / 2) - 1
32位除法明显快于64位除法。例如,在Ivy Bridge上,32位IDIV需要19-26个时钟,而64位IDIV需要28-103个时钟延迟。
无符号版本也比签名快,因为除以2是无符号情况下的简单位移,并且没有大小扩展调用(CDQ,CQO)。
无符号情况下的是带符号的
中的简单位移