为什么在这里首选位运算和乘法而不是条件?
Why bitoperation and multiplication is prefered here over a condition?
我在源代码中找到了这个位操作:
A = 0b0001;
B = 0b0010;
C = 0b0100;
flags |= !!(flags & (A | B)) * C;
我不明白,为什么要用这么复杂的表达方式。 flags & (A | B)
过滤 flags
到 A | B
。现在它被转换为 true
,如果 flags 被设置为任何东西,否则 false
。 true * C == C
和 false * C == 0
。只使用 flags = flags ? flags | C
是不是比较慢?
bitwise:
mov eax, edi
mov edx, edi
or edx, 4
test al, 3
cmovne eax, edx
ret
condition:
mov eax, edi
mov edx, 0
or eax, 4
test edi, edi
cmove eax, edx
ret
Clang 6.0 also eliminate the redundant call:
bitwise:
xor eax, eax
test dil, 3
setne al
shl eax, 2
or eax, edi
ret
condition:
mov eax, edi
or eax, 4
test edi, edi
cmove eax, edi
ret
我在监督什么吗?
您在您的版本中遗漏了带有 A|B
的测试(您链接的 ). And you're zeroing flags
instead of leaving it unmodified if the test fails. Remember there can be other set flags in other bits which need to be unaffected by setting a flag C
that's required/implied if either of A
or B
are present. (See the comment in the original source:Add TRANSFER_BIT if missing (implied)
)
而仅替换 boolean-multiply hack 的等效三元运算符将是 flags |= (flags & (A|B)) ? C : 0
.
另一种方法是 flags = (flags & (A|B)) ? flags|C : flags
,编译效果更好。
我修复了您在 Godbolt 上的代码以使用正确的表达式,是的,它使用 gcc/clang 和 MSVC 编译为 better-looking asm。在使用 flags
.
做更多事情的更大函数的上下文中,这可能仍然是正确的
好像是这个版本compiles the best with gcc/clang/MSVC:
int condition(int flags) {
flags = (flags&(A|B)) ? flags | C : flags;
return flags;
}
; gcc/clang both emit
mov eax, edi
or eax, 4
test dil, 3
cmove eax, edi
ret
和 MSVC 类似,但是有一个 and
而不是 test
,还有一个额外的 mov
因为破坏性的 and
.
Clang 6.0 also eliminate the redundant call
什么call
?此代码中没有函数调用。您是说 clang 避免了 gcc 使用的冗余 mov edx, edi
吗?是的,gcc 在那里很蠢。
为什么会有人按照他们的方式来写?
也许他们过去的经验是使用较旧的编译器,这些编译器不能很好地处理三元运算符,或者使用没有 cmov
而实际上使用 *
运算符的非 x86 平台源代码是获得无分支 asm 的唯一方法之一。
我猜是谁写的,不久前就学会了这个技巧,并且仍然使用它而不是三元运算符。这不是一个好主意,也不会导致更好的代码。
我在源代码中找到了这个位操作:
A = 0b0001;
B = 0b0010;
C = 0b0100;
flags |= !!(flags & (A | B)) * C;
我不明白,为什么要用这么复杂的表达方式。 flags & (A | B)
过滤 flags
到 A | B
。现在它被转换为 true
,如果 flags 被设置为任何东西,否则 false
。 true * C == C
和 false * C == 0
。只使用 flags = flags ? flags | C
是不是比较慢?
bitwise:
mov eax, edi
mov edx, edi
or edx, 4
test al, 3
cmovne eax, edx
ret
condition:
mov eax, edi
mov edx, 0
or eax, 4
test edi, edi
cmove eax, edx
ret
Clang 6.0 also eliminate the redundant call:
bitwise:
xor eax, eax
test dil, 3
setne al
shl eax, 2
or eax, edi
ret
condition:
mov eax, edi
or eax, 4
test edi, edi
cmove eax, edi
ret
我在监督什么吗?
您在您的版本中遗漏了带有 A|B
的测试(您链接的 flags
instead of leaving it unmodified if the test fails. Remember there can be other set flags in other bits which need to be unaffected by setting a flag C
that's required/implied if either of A
or B
are present. (See the comment in the original source:Add TRANSFER_BIT if missing (implied)
)
而仅替换 boolean-multiply hack 的等效三元运算符将是 flags |= (flags & (A|B)) ? C : 0
.
另一种方法是 flags = (flags & (A|B)) ? flags|C : flags
,编译效果更好。
我修复了您在 Godbolt 上的代码以使用正确的表达式,是的,它使用 gcc/clang 和 MSVC 编译为 better-looking asm。在使用 flags
.
好像是这个版本compiles the best with gcc/clang/MSVC:
int condition(int flags) {
flags = (flags&(A|B)) ? flags | C : flags;
return flags;
}
; gcc/clang both emit
mov eax, edi
or eax, 4
test dil, 3
cmove eax, edi
ret
和 MSVC 类似,但是有一个 and
而不是 test
,还有一个额外的 mov
因为破坏性的 and
.
Clang 6.0 also eliminate the redundant call
什么call
?此代码中没有函数调用。您是说 clang 避免了 gcc 使用的冗余 mov edx, edi
吗?是的,gcc 在那里很蠢。
为什么会有人按照他们的方式来写?
也许他们过去的经验是使用较旧的编译器,这些编译器不能很好地处理三元运算符,或者使用没有 cmov
而实际上使用 *
运算符的非 x86 平台源代码是获得无分支 asm 的唯一方法之一。
我猜是谁写的,不久前就学会了这个技巧,并且仍然使用它而不是三元运算符。这不是一个好主意,也不会导致更好的代码。