这是与@RocketRoy 就his answer 讨论的后续内容,但它可能对任何想要比较这些结果的人有用。
tl;dr 据我所见,Roy 的方法 ((0xFFFFFFFF == (x | 0xFFFFFFFE)) 并没有像 mod 方法那样完全优化到 x & 1,但实际上运行时间应该会变成在所有情况下都相等。
所以,首先我使用Compiler Explorer比较编译输出:
功能测试:
int isOdd_mod(unsigned x) {
return (x % 2);
}
int isOdd_and(unsigned x) {
return (x & 1);
}
int isOdd_or(unsigned x) {
return (0xFFFFFFFF == (x | 0xFFFFFFFE));
}
带有 -O3 的 CLang 3.9.0:
isOdd_mod(unsigned int): # @isOdd_mod(unsigned int)
and edi, 1
mov eax, edi
ret
isOdd_and(unsigned int): # @isOdd_and(unsigned int)
and edi, 1
mov eax, edi
ret
isOdd_or(unsigned int): # @isOdd_or(unsigned int)
and edi, 1
mov eax, edi
ret
带有 -O3 的 GCC 6.2:
isOdd_mod(unsigned int):
mov eax, edi
and eax, 1
ret
isOdd_and(unsigned int):
mov eax, edi
and eax, 1
ret
isOdd_or(unsigned int):
or edi, -2
xor eax, eax
cmp edi, -1
sete al
ret
感谢 CLang,它意识到这三种情况在功能上是相同的。但是,Roy 的方法没有在 GCC 中进行优化,所以 YMMV。
与 Visual Studio 类似;检查这三个函数的反汇编版本 x64 (VS2015),我可以看到比较部分对于“mod”和“and”情况是相等的,而对于 Roy 的“or”情况来说则稍大:
// x % 2
test bl,1
je (some address)
// x & 1
test bl,1
je (some address)
// Roy's bitwise or
mov eax,ebx
or eax,0FFFFFFFEh
cmp eax,0FFFFFFFFh
jne (some address)
但是,在运行实际基准测试以比较这三个选项(普通 mod、按位或、按位与)后,结果完全相等(同样,Visual Studio 2005 x86/x64,发布版本,未附加调试器)。
对于and 和mod 情况,发布程序集使用test 指令,而Roy 的情况使用cmp eax,0FFFFFFFFh 方法,但它经过大量展开和优化,因此在实践中没有区别。
我运行 20 次后的结果(i7 3610QM,Windows 10 电源计划设置为高性能):
[测试:普通模式 2] 平均时间:689.29 毫秒(相对差异:+0.000%)
[测试:按位或]平均时间:689.63 毫秒(相对差异:+0.048%)
[测试:按位和]平均时间:687.80 毫秒(相对差异:-0.217%)
这些选项之间的差异小于 0.3%,因此很明显组装在所有情况下都是相等的。
如果有人想尝试,这里是代码,但需要注意的是我只在 Windows 上测试过它(检查 #if LINUX 条件以获取 get_time 定义并在需要时实现它,取自 this answer)。
#include <stdio.h>
#if LINUX
#include <sys/time.h>
#include <sys/resource.h>
double get_time()
{
struct timeval t;
struct timezone tzp;
gettimeofday(&t, &tzp);
return t.tv_sec + t.tv_usec*1e-6;
}
#else
#include <windows.h>
double get_time()
{
LARGE_INTEGER t, f;
QueryPerformanceCounter(&t);
QueryPerformanceFrequency(&f);
return (double)t.QuadPart / (double)f.QuadPart * 1000.0;
}
#endif
#define NUM_ITERATIONS (1000 * 1000 * 1000)
// using a macro to avoid function call overhead
#define Benchmark(accumulator, name, operation) { \
double startTime = get_time(); \
double dummySum = 0.0, elapsed; \
int x; \
for (x = 0; x < NUM_ITERATIONS; x++) { \
if (operation) dummySum += x; \
} \
elapsed = get_time() - startTime; \
accumulator += elapsed; \
if (dummySum > 2000) \
printf("[Test: %-12s] %0.2f ms\r\n", name, elapsed); \
}
void DumpAverage(char *test, double totalTime, double reference)
{
printf("[Test: %-12s] AVERAGE TIME: %0.2f ms (Relative diff.: %+6.3f%%)\r\n",
test, totalTime, (totalTime - reference) / reference * 100.0);
}
int main(void)
{
int repeats = 20;
double runningTimes[3] = { 0 };
int k;
for (k = 0; k < repeats; k++) {
printf("Run %d of %d...\r\n", k + 1, repeats);
Benchmark(runningTimes[0], "Plain mod 2", (x % 2));
Benchmark(runningTimes[1], "Bitwise or", (0xFFFFFFFF == (x | 0xFFFFFFFE)));
Benchmark(runningTimes[2], "Bitwise and", (x & 1));
}
{
double reference = runningTimes[0] / repeats;
printf("\r\n");
DumpAverage("Plain mod 2", runningTimes[0] / repeats, reference);
DumpAverage("Bitwise or", runningTimes[1] / repeats, reference);
DumpAverage("Bitwise and", runningTimes[2] / repeats, reference);
}
getchar();
return 0;
}