计数';1';C中的数字
我的任务是打印从2到N的所有整数(二进制中的“1”大于“0”)计数';1';C中的数字,c,binary,C,Binary,我的任务是打印从2到N的所有整数(二进制中的“1”大于“0”) 最后,我搞砸了,不知道什么更好。你对此有何看法?甚至比你老师的建议更好: if( n & 1 ) { ++ CountOnes; } else { ++ CountZeros; } n%2有一个隐式除法操作,编译器可能会对其进行优化,但您不应该依赖它-除法是一个复杂的操作,在某些平台上需要更长的时间。此外,只有两个选项1或0,因此如果它不是一,则为零-无需在else块中进行第
最后,我搞砸了,不知道什么更好。你对此有何看法?甚至比你老师的建议更好:
if( n & 1 ) {
++ CountOnes;
}
else {
++ CountZeros;
}
n%2
有一个隐式除法操作,编译器可能会对其进行优化,但您不应该依赖它-除法是一个复杂的操作,在某些平台上需要更长的时间。此外,只有两个选项1或0,因此如果它不是一,则为零-无需在else
块中进行第二次测试
您的原始代码过于复杂,难以理解。如果要评估算法的“效率”,请考虑每次迭代执行的操作次数和迭代次数。还包括涉及的变量数量。在您的例子中,每个迭代有10个操作和三个变量(但是您忽略了计算零,所以您需要四个变量来完成赋值)。以下是:
unsigned int n = x; // number to be modifed
int ones = 0 ;
int zeroes = 0 ;
while( i > 0 )
{
if( (n & 1) != 0 )
{
++ones ;
}
else
{
++zeroes ;
}
n >>= 1 ;
}
只有7个操作(将
>=
计算为两个移位和赋值)。也许更重要的是,它更容易理解。我在下面的实验中使用了gcc编译器。您的编译器可能不同,因此您可能需要做一些不同的事情来获得类似的效果
当试图找出执行某些操作的最佳方法时,您需要查看编译器生成的代码类型。看看CPU手册,看看在特定的体系结构上哪些操作是快的,哪些是慢的。虽然有一般性的指导方针。当然,如果有方法可以减少CPU必须执行的指令数量
我决定向您展示几种不同的方法(并非详尽无遗),并向您提供一个示例,说明如何着手手动优化小函数(如此函数)。有更复杂的工具可以帮助处理更大、更复杂的功能,但是这种方法应该适用于几乎任何东西:
注
所有汇编代码都是使用以下方法生成的:
gcc-O99-foo-fprofile生成foo.c
接
gcc-O99-o foo-fprofile使用foo.c
On-fprofile生成
双编译使gcc真正让gcc工作起来(尽管-O99很可能已经这样做了),但是根据您可能使用的gcc版本,差异可能会有所不同
关于它:
方法一(你)
以下是对函数的分解:
CountOnes_you:
.LFB20:
.cfi_startproc
xorl %eax, %eax
testl %edi, %edi
je .L5
.p2align 4,,10
.p2align 3
.L4:
movl %edi, %edx
xorl %ecx, %ecx
andl $-2, %edx
subl %edx, %edi
cmpl $1, %edi
movl %edx, %edi
sete %cl
addl %ecx, %eax
shrl %edi
jne .L4
rep ret
.p2align 4,,10
.p2align 3
.L5:
rep ret
.cfi_endproc
一瞥
循环中大约有9条指令,直到循环退出
方法二(教师)
下面是一个使用教师算法的函数:
int CountOnes_teacher(unsigned int x)
{
unsigned int one_count = 0;
while(x) {
if(x%2)
++one_count;
x >>= 1;
}
return one_count;
}
下面是对它的分解:
CountOnes_teacher:
.LFB21:
.cfi_startproc
xorl %eax, %eax
testl %edi, %edi
je .L12
.p2align 4,,10
.p2align 3
.L11:
movl %edi, %edx
andl $1, %edx
cmpl $1, %edx
sbbl $-1, %eax
shrl %edi
jne .L11
rep ret
.p2align 4,,10
.p2align 3
.L12:
rep ret
.cfi_endproc
一目了然:
循环中的5条指令,直到循环退出
方法三
以下是克雷尼根的方法:
int CountOnes_K(unsigned int x) {
unsigned int count;
for(count = 0; ; x; count++) {
x &= x - 1; // clear least sig bit
}
return count;
}
以下是拆解:
CountOnes_k:
.LFB22:
.cfi_startproc
xorl %eax, %eax
testl %edi, %edi
je .L19
.p2align 4,,10
.p2align 3
.L18:
leal -1(%rdi), %edx
addl $1, %eax
andl %edx, %edi
jne .L18 ; loop is here
rep ret
.p2align 4,,10
.p2align 3
.L19:
rep ret
.cfi_endproc
一瞥
循环中有3条指令
在继续之前,请发表一些评论
正如您所见,当您使用%
进行计数时(您和您的老师都使用了这种方法),编译器并没有真正使用最佳方法
Krenighan方法非常优化,循环中的操作数最少)。将克里尼根与天真的计数方法进行比较是很有指导意义的,但表面上看起来可能是一样的,实际上并非如此
for (c = 0; v; v >>= 1)
{
c += v & 1;
}
这种方法比克里尼汉更糟糕。这里,如果你说第32位设置,这个循环将运行32次,而Krenighan的不会
但是,所有这些方法仍然相当低,因为它们是循环的
如果我们将一些其他的(隐式的)知识结合到我们的算法中,我们就可以一起摆脱循环。它们是,1,以位表示的数字大小,以位表示的字符大小。有了这些片段,并且意识到如果我们有一个64位寄存器,我们可以过滤掉14、24或32位块中的位
例如,如果我们看一个14位的数字,那么我们可以简单地通过以下方式计算位:
(n * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;
对0x0
和0x3fff
之间的所有数字只使用一次
对于24位,我们使用14位,然后对剩余的10位使用类似的内容:
((n & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f
+ (((n & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL)
% 0x1f;
但是我们可以通过实现上面数字中的模式来推广这个概念,并认识到幻数实际上只是恭维(仔细看十六进制数0x8000+0x400+0x200+0x1)
我们可以在这里概括然后缩小思路,为我们提供计算位(最多128位)(无循环)O(1)的最优化方法:
CountOnes\u最佳(无符号整数n){
const unsigned char_bits=sizeof(unsigned char)>1)&(T)~(T)0/3);//将n重新用作临时变量
n=(n&(T)~(T)0/15*3)+(n>>2)&(T)~(T)0/15*3);
n=(n+(n>>4))&(T)~(T)0/255*15;
返回(T)(n*((T)~(T)0/255))>>(sizeof(T)-1)*字符位;
}
最佳伯爵:
.LFB23:
.cfi_startproc
移动%edi,%eax
shrl%eax
andl$1431655765,%eax
子目录%eax,%edi
移动%edi,%edx
shrl$2,%edi
andl$858993459,%edx
andl$858993459,%edi
添加%edx,%edi
移动%edi,%ecx
shrl$4,%ecx
添加%edi,%ecx
和252645135美元,ecx%
I全额$16843009,%ecx,%eax
shrl$24,%eax
ret
.cfi_endproc
这可能是一个跳跃(你是怎么从以前到现在的),但只是花你的时间去看看它
AMD Athelon的软件优化指南中首次提到了最优化的方法™ 64和Opteron™ 处理器,我的那个URL坏了。这也是很好的解释上非常优秀
我强烈建议您仔细阅读该页面的内容,这真是一本很棒的书。1不总是大于0吗?困惑,一怎么算零?你的意思是最重要的一个后面的零?@alk我猜这是关于popcnt大于或大于值宽度的一半。否则(n%2==
(n * 0x200040008001ULL & 0x111111111111111ULL) % 0xf;
((n & 0xfff) * 0x1001001001001ULL & 0x84210842108421ULL) % 0x1f
+ (((n & 0xfff000) >> 12) * 0x1001001001001ULL & 0x84210842108421ULL)
% 0x1f;
CountOnes_best(unsigned int n) {
const unsigned char_bits = sizeof(unsigned char) << 3;
typedef __typeof__(n) T; // T is unsigned int in this case;
n = n - ((n >> 1) & (T)~(T)0/3); // reuse n as a temporary
n = (n & (T)~(T)0/15*3) + ((n >> 2) & (T)~(T)0/15*3);
n = (n + (n >> 4)) & (T)~(T)0/255*15;
return (T)(n * ((T)~(T)0/255)) >> (sizeof(T) - 1) * char_bits;
}
CountOnes_best:
.LFB23:
.cfi_startproc
movl %edi, %eax
shrl %eax
andl $1431655765, %eax
subl %eax, %edi
movl %edi, %edx
shrl $2, %edi
andl $858993459, %edx
andl $858993459, %edi
addl %edx, %edi
movl %edi, %ecx
shrl $4, %ecx
addl %edi, %ecx
andl $252645135, %ecx
imull $16843009, %ecx, %eax
shrl $24, %eax
ret
.cfi_endproc