慢速异或运算符

6

编辑:事实上,我的时间代码存在奇怪的错误,导致了这些结果。当我修复了我的错误后,智能版本的速度如预期一样更快。我的时间代码如下:

bool x = false;
before = now();
for (int i=0; i<N; ++i) {
  x ^= smart_xor(A[i],B[i]);
}
after = now();

我曾经使用^=来阻止编译器优化for循环。但我认为^=与两个异或函数的交互方式有些奇怪。我将计时代码更改为简单地填充一个异或结果数组,然后在计时代码之外使用该数组进行计算。这样就解决了问题。

我应该删除这个问题吗?

结束编辑

我定义了两个C++函数如下:

bool smart_xor(bool a, bool b) {
  return a^b;
}

bool dumb_xor(bool a, bool b) {
  return a?!b:b;
}

我的时间测试表明,dumb_xor() 稍微快一些(内联时为 1.31ns vs 1.90ns ,未内联时为 1.92ns vs 2.21ns)。这让我感到困惑,因为 ^ 运算符应该是一个单机操作。我想知道是否有人可以解释这个问题。
汇编代码如下(未内联时):
    .file   "xor.cpp"
    .text
    .p2align 4,,15
.globl _Z9smart_xorbb
    .type   _Z9smart_xorbb, @function
_Z9smart_xorbb:
.LFB0:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    %esi, %eax
    xorl    %edi, %eax
    ret
    .cfi_endproc
.LFE0:
    .size   _Z9smart_xorbb, .-_Z9smart_xorbb
    .p2align 4,,15
.globl _Z8dumb_xorbb
    .type   _Z8dumb_xorbb, @function
_Z8dumb_xorbb:
.LFB1:
    .cfi_startproc
    .cfi_personality 0x3,__gxx_personality_v0
    movl    %esi, %edx
    movl    %esi, %eax
    xorl    $1, %edx
    testb   %dil, %dil
    cmovne  %edx, %eax
    ret
    .cfi_endproc
.LFE1:
    .size   _Z8dumb_xorbb, .-_Z8dumb_xorbb
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section        .note.GNU-stack,"",@progbits

我正在使用Intel Xeon X5570上的g++ 4.4.3-4ubuntu5。我使用了-O3进行编译。


纳秒很可能太小,无法进行任何有意义的比较。 - yngccc
2
你可能需要展示你的计时代码。这很容易出错。 - Drew Dormann
2
我更喜欢使用a != b而不是a ? !b : b - Pubby
1
与问题无关 - 为什么不使用return a != b; - Drew Dormann
我认为你不应该删除你的问题,只是你必须意识到,在非常快速的紧密循环中测量某些东西可能不会显示准确的结果。 - Mats Petersson
2个回答

5
我认为您没有正确地对您的代码进行基准测试。
我们可以在生成的汇编代码中看到,您的 smart_xor 函数是:
movl    %esi, %eax
xorl    %edi, %eax

您的 dumb_xor 函数如下:

movl    %esi, %edx
movl    %esi, %eax
xorl    $1, %edx
testb   %dil, %dil
cmovne  %edx, %eax

很明显,第一个会更快。
如果不是这样,那么你就有基准测试问题了。
所以你可能需要调整你的基准测试代码... 记住,你需要运行很多次才能得到一个好且有意义的平均值。

4
考虑到您“愚蠢的异或”代码明显更长(大多数指令都依赖于先前的指令,因此无法并行运行),我怀疑您的结果存在某种测量误差。
编译器将需要为“智能异或”的离线版本生成两个指令,因为数据输入的寄存器不是给出返回结果的寄存器,所以数据必须从EDI和ESI移动到EAX。在内联版本中,代码应该能够使用调用之前数据所在的任何寄存器,并且如果代码允许,结果保留在原来的寄存器中。
调用函数是离线执行至少与函数中实际代码一样长的时间。
如果您展示了用于基准测试的测试工具将有所帮助...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接