好吧,看看 2 种方法来获得“模 3”循环计数器的下一个值。
int next1(int n) {
return (n + 1) % 3;
}
int next2(int n) {
return n == 2 ? 0 : n + 1;
}
我已经用 gcc -O3 选项(对于常见的 x64 架构)和 -s 来编译它以获取汇编代码。
第一个函数的代码做了一些无法解释的魔法(*)来避免除法,无论如何都使用乘法:
addl $1, %edi
movl $1431655766, %edx
movl %edi, %eax
imull %edx
movl %edi, %eax
sarl $31, %eax
subl %eax, %edx
leal (%rdx,%rdx,2), %eax
subl %eax, %edi
movl %edi, %eax
ret
并且比第二个函数要长得多(我敢打赌要慢得多):
leal 1(%rdi), %eax
cmpl $2, %edi
movl $0, %edx
cmove %edx, %eax
ret
因此,“(现代)编译器无论如何都比你做得更好”并不总是正确的。
有趣的是,使用 4 而不是 3 的相同实验导致第一个函数的 and-masking
addl $1, %edi
movl %edi, %edx
sarl $31, %edx
shrl $30, %edx
leal (%rdi,%rdx), %eax
andl $3, %eax
subl %edx, %eax
ret
但它仍然,而且在很大程度上,不如第二个版本。
更明确地说明做事的正确方法
int next3(int n) {
return (n + 1) & 3;;
}
产生更好的结果:
leal 1(%rdi), %eax
andl $3, %eax
ret
(*) 好吧,没那么复杂。乘以倒数。计算整数常数 K = (2^N)/3,以获得足够大的 N 值。现在,当您想要 X/3 的值时,而不是除以 3,计算 X*K,并将其移位 N位置向右。