C# 为什么可以';t如果(!A&;!B)优化为单个测试指令?
C# 为什么可以';t如果(!A&;!B)优化为单个测试指令?,c#,c++,assembly,x86,compiler-optimization,C#,C++,Assembly,X86,Compiler Optimization,如果(!A&&!B)看起来应该编译为 mov eax, dword ptr[esp + A_offset] test eax, dword ptr[esp + B_offset] jne ~~~~~~~~~~ 编译器实际生成 mov eax, dword ptr[esp + A_offset] test eax, eax jne ~~~~~~~~~~ mov eax, dword ptr[esp + B_offset] test eax, eax j
如果(!A&&!B)
看起来应该编译为
mov eax, dword ptr[esp + A_offset]
test eax, dword ptr[esp + B_offset]
jne ~~~~~~~~~~
编译器实际生成
mov eax, dword ptr[esp + A_offset]
test eax, eax
jne ~~~~~~~~~~
mov eax, dword ptr[esp + B_offset]
test eax, eax
jne ~~~~~~~~~~
看到这里了吗
8B 45 F8 mov eax,dword ptr [b]
83 7D FC 00 cmp dword ptr [a],0
75 04 jne main+32h (0A71072h)
85 C0 test eax,eax
75 00 jne main+32h (0A71072h)
为什么不使用单个测试指令来保存分支和指令?否。
TEST
指令执行操作数的按位and运算,并根据结果设置标志,请参阅
因此,编译器生成的代码是正确的。因为
让我们注意上面的代码
如果A是真实的(不是0),!A&&!B
变为0(假)
。
对,您不必检查B的值。
它应该跳过(跳转)if语句的代码块
mov eax, dword ptr[esp + A_offset]
test eax, eax ; If `A & A`
jne ~~~~~~~~~~ ; is not 0(If A is not 0), skip this if-codeblock.
mov eax, dword ptr[esp + B_offset] ; Otherwise,
test eax, eax ; If `B & B`
jne ~~~~~~~~~~ ; is not 0(If B is not 0), skip this if-codeblock.
...... ; Both A and B are 0, and `!A && !B` is `1(TRUE)`! Run the if-codeblock.
加上:
看来你的代码错了
mov eax, dword ptr[esp + A_offset]
mov ebx, dword ptr[esp + B_offset]
test eax, ebx ; `A & B`
jne ~~~~~~~~~~ ; If `A & B != 0`, skip this code-block for the if statement.
...... ; In other words, this code-block will be run when `A & B == 0`,
; which will be `TRUE` when A is 1(0b00000001) and B is 2(0b00000010).
如果A=1,B=2怎么办?然后测试会给出错误的答案。@Yuval这不是重复的问题(至少不是那个问题)。这里的问题是关于
(a!=0)和(b!=0
与(a&b)!=0
,而不是懒惰的评估。如果(!a&&!b)
只有当a
和b
都为零(如果(!(a&b))
)时才是真的。所以如果你是“代码高尔夫”,这可以通过moveax、或来完成
jnz not_true
。编译器使用了更多的指令,但分离了A和B之间的直接依赖关系,特定的非零测试可以按任何顺序进行,无论哪个值首先从内存中到达(如果“B”分支作为预测推测性地无序运行-我不知道CPU在重新排序方面是否先进)。如果A和B都是同一范围内的局部变量,我打赌0.01美元我的版本是最优的。;@PeterCordes例如i8051和克隆具有位数据类型和面向位的指令。然而,位变量的数量被硬件限制为256。根据我的经验,他们最好实施一些有用的数学。可能是因为缺乏实用性,在更大的CPU中丢弃面向位的指令GCC已经变成了!a&&!b进入!(a | b)
当它知道b可以安全阅读时。Peter,我能想到的最接近的操作是1)对simd向量的缩减操作和2)谓词操作(设置它,使操作b!=0仅在a!=0返回true时执行)。但我不知道有多少指令集…谢谢。我只是感到困惑,如果有一种有效的方法可以同时测试两者,编译器就会这样做。请注意,OP的代码在测试a
之前已经加载到寄存器中,因此编译器的决策与避免工作或短路评估无关,而与bool(a&b)
!=<代码>A&&B
当A和B为非零且具有非交叉位模式时。还要注意gcc开发者Marc Glisse的评论,gcc变成了!a&&!b进入!(a | b)
当它知道b
可以安全阅读时。@PeterCordes好吧,公平地说,GCC在b
不安全阅读时不进行优化的原因是因为短路评估意味着如果a
是真的,它永远不会被访问。@RossRidge:对,显然,这是维护C源代码语义的先决条件。但我的观点是,问题中真正的编译器输出(最后一个代码块)在第一次测试之前进行了两次加载,证明了它们都可以安全访问,并且它没有试图避免,所以这个答案的第一个假设是错误的。
mov eax, dword ptr[esp + A_offset]
mov ebx, dword ptr[esp + B_offset]
test eax, ebx ; `A & B`
jne ~~~~~~~~~~ ; If `A & B != 0`, skip this code-block for the if statement.
...... ; In other words, this code-block will be run when `A & B == 0`,
; which will be `TRUE` when A is 1(0b00000001) and B is 2(0b00000010).