通常希望出于各种原因检查全数的奇数,一个这样的示例是针对数据网格,其中每个奇数行是不同的颜色以便于阅读。程序员经常使用公式
实现这样的功能odd = n mod 2 != 0
或反过来:
even = n mod 2 == 0
但这种方法很慢,因为它需要FPU来计算除法的余数。
我还看到了开发人员编写以下内容的代码:
x = n / 2
odd = x mod 2
有更好的方法吗?
答案 0 :(得分:0)
这个问题的答案相当简单,但经常被忽视,请注意,这只适用于整数,而不是浮点数。
要使FPU参与x86 CPU以执行除法/模数计算,单精度至少需要17个时钟周期,双精度需要34个时钟周期。
因此,我们可以很好地了解上面代码中更复杂的示例需要多少个周期(最好不计算CPU加载/存储操作)。
x = n / 2 17 cycles
odd = x mod 2 17 cycles
---------
34 cycles
因为计算机用二进制表示整数,我们可以通过检查第一位是否设置来检查数字是否为奇数,这将适用于任何支持逐位运算的语言。
例如:
Psuedo Code: odd = n and 1
C/C++/Java : odd = n & 1;
掩模操作仅需要1个周期,这取决于FPU模式,性能提升为每分割/模数操作17x至34x 。
对于第二示例中的2除法,可以使用位操作来执行,实际上,可以使用位右移操作来执行2次幂的任何除法。例如:
Psuedo Code: x = n shr 1
C/C++/Java : x = n >> 1;
这会将n
中的所有位向右移动一个,从而将除以2,如果它向右移2,则将除以4。这可以反过来乘以在2的幂中,但通常不以这种方式使用,因为乘法运算同样快。
如果我们将这两种技术结合起来,我们可以将第二个例子改为:
Psuedo Code:
x = n shr 1
odd = x and 1
C/C++/Java:
x = n >> 1;
odd = x & 1;
这可以进一步简化为
Psuedo Code: odd = (n shr 1) and 1
C/C++/Java : odd = (n >> 1) & 1;
与使用FPU的版本相比,此版本将使用总共两个时钟周期来执行数学运算,这是性能提升1700%。
许多编译器都知道这些技巧,并且只会以适合您的方式编译代码,但许多脚本语言并不那么聪明,而且并非所有编译器都是相同的。
例如,编译器可能无法获得以下示例用法:
int doMath(int x, int y) {
return x / y;
}
doMath(10, 2);
doMath(20, 4);
doMath(30, 8);
但是,如果它是使用权力来代替,那么它总是会更快,即:
int doMath(int x, int y) {
return x >> y;
}
doMath(10, 1);
doMath(20, 2);
doMath(30, 3);
如果您学习并利用这些技巧,您将编写非常有效的代码,无论何时编译都能为您创建良好的输出,尤其是如果您经常在Microsoft / Intel和GCC之间交叉编译代码在平台无关项目中。