Rag*_*nar 6 c++ performance bit-manipulation
今天,我注意到,有几个简单的按位和算术运算的速度之间显著不同int,unsigned,long long和unsigned long long我的64位PC.
特别是,下面的循环是两次快速的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;
}
Run Code Online (Sandbox Code Playgroud)
(完整代码在这里)
下面是定时(以秒计)(为g++ -O,-O2和-O3):
1.834207723 (int)
3.054731598 (long long)
1.584846237 (unsigned)
2.201142018 (unsigned long long)
Run Code Online (Sandbox Code Playgroud)
这些时间非常一致(即1%的保证金).没有-O旗帜,每一个都慢一秒,但相对速度是相同的.
这有明显的原因吗?矢量可能是它的32位的类型,但我看不到的地方之间的巨大差异long long,并unsigned long long从何而来.某些类型的某些操作比其他类型的操作慢得多,或者64位类型的速度通常比较慢(即使在64位架构上)?
对于那些感兴趣的人来说,这个循环遍历所有{1,2,...,30}15个元素的子集.这是通过循环(按顺序)完成所有小于1<<3015位设置的整数来完成的.对于目前的情况,这是155117520次迭代.我不知道这个片段的来源了,但这篇文章解释了更多.
从汇编代码看,当类型未签名时,可以使除法更快.我认为这是有道理的,因为我们不必考虑符号位.
此外,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));
}
Run Code Online (Sandbox Code Playgroud)
这在上面发布的代码的大约三分之一的时间内运行,并且对所有四种类型使用相同的时间.
代码中最慢的操作是
mask |= (lz / lo / 2) - 1
Run Code Online (Sandbox Code Playgroud)
32位除法明显快于64位除法.例如,在Ivy Bridge上,32位IDIV需要19-26个时钟,而64位IDIV需要28-103个时钟延迟.
无符号版本也比签名更快,因为除以2是在无符号情况下的简单位移,并且没有大小扩展调用(CDQ,CQO).
在无符号的情况下,在签名时是简单的位移
[1] http://www.agner.org/optimize/instruction_tables.pdf