以下是我声称执行完全相同操作的两个函数:
bool fast(int x)
{
return x & 4242;
}
bool slow(int x)
{
return x && (x & 4242);
}
从逻辑上讲,它们做同样的事情,为了100%确定,我编写了一个测试,通过它们运行了所有40亿个可能的输入,并且它们匹配。(x
fast:
andl $4242, %edi
setne %al
ret
slow:
xorl %eax, %eax
testl %edi, %edi
je .L3
andl $4242, %edi
setne %al
.L3:
rep
ret
我很惊讶GCC不能做出逻辑上的跳跃来消除多余的测试。我用-O2、-O3和-Os尝试了g 4.4.3和4.7.2,都生成了相同的代码。平台是Linux x86_64。
有人可以解释为什么GCC不应该足够聪明,在这两种情况下生成相同的代码吗?
编辑以添加测试线束:
#include <cstdlib>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
// make vector filled with numbers starting from argv[1]
int seed = atoi(argv[1]);
vector<int> v(100000);
for (int j = 0; j < 100000; ++j)
v[j] = j + seed;
// count how many times the function returns true
int result = 0;
for (int j = 0; j < 100000; ++j)
for (int i : v)
result += slow(i); // or fast(i), try both
return result;
}
我用-O3在Mac OS上用clang 5.1测试了以上内容。使用< code>fast()
花费了2.9秒,使用< code>slow()花费了3.8秒。如果我使用一个全零的向量,这两个函数的性能没有明显的差别。
其他编译器:
进行优化
这就是你的代码在ARM中的样子,当输入它0时,它应该使慢
速运行得更快。
fast(int):
movw r3, #4242
and r3, r0, r3
adds r0, r3, #0
movne r0, #1
bx lr
slow(int):
cmp r0, #0
bxeq lr
movw r3, #4242
and r3, r0, r3
adds r0, r3, #0
movne r0, #1
bx lr
然而,无论如何,当您开始使用这些琐碎的函数时,GCC会很好地进行优化。
bool foo() {
return fast(4242) && slow(42);
}
成为
foo():
mov r0, #1
bx lr
我的观点是,有时这样的代码需要更多的上下文来进一步优化,那么为什么优化器的实施者(改进者!)应该费心呢?
另一个例子:
bool bar(int c) {
if (fast(c))
return slow(c);
}
成为
bar(int):
movw r3, #4242
and r3, r0, r3
cmp r3, #0
movne r0, #1
bxne lr
bx lr
您是正确的,这似乎是优化器中的一个缺陷,可能是一个彻底的错误。
考虑:
bool slow(int x)
{
return x && (x & 4242);
}
bool slow2(int x)
{
return (x & 4242) && x;
}
GCC 4.8.1发出的程序集(-O3):
slow:
xorl %eax, %eax
testl %edi, %edi
je .L2
andl $4242, %edi
setne %al
.L2:
rep ret
slow2:
andl $4242, %edi
setne %al
ret
换句话说,slow2
被错误地命名了。
我只是偶尔为GCC贡献补丁,所以我的观点是否有任何分量是值得商榷的:-)。但在我看来,GCC优化其中一个而不是另一个肯定是奇怪的。我建议提交错误报告。
[更新]
令人惊讶的是,微小的变化似乎会产生很大的影响。例如:
bool slow3(int x)
{
int y = x & 4242;
return y && x;
}
...再次生成“慢速”代码。我对这种行为没有假设。
您可以在此处的多个编译器上试验所有这些。
到底为什么它应该能够优化代码?您假设任何有效的转换都会完成。这根本不是优化器的工作方式。它们不是人工智能。它们只是通过参数化替换已知模式来工作。E、 g.“公共子表达式消除”扫描表达式中的公共子表达式,如果不改变副作用,则向前移动它们。
(顺便说一句,CSE表明优化器已经非常清楚在可能存在副作用的情况下允许什么代码移动。他们知道你必须小心
总之,你认为哪种模式适用于这里?
问题内容: 什么逻辑运营商之间的不同,和位类似物,在使用?各种解决方案的效率是否存在差异? 问题答案: 逻辑运算符对逻辑值进行运算,而按位运算符对整数位进行运算。停止考虑性能,并按其预期目的使用它们。
这是一个使用ValArray的简单c程序: 如果我像这样编译并运行它: 产出如预期: 但是,如果我像这样编译和运行它: 输出为: 如果使用优化参数,也会发生同样的情况。 GCC版本是(Archlinux最新版本): 但是,如果我尝试叮当,两者 和 产生相同的正确结果: clang版本是: 我还尝试了在Debian上使用GCC 4.9.2,其中可执行文件会产生正确的结果。 这是GCC中可能存在的错误
问题内容: 下面的两个语句是否等效? 和 我可以使用某种真值表来验证这一点吗? 问题答案: 优先于,因此,即使 与…不同 因为那将被执行为 并且想要使它们相同,是以下内容(使用括号覆盖优先级规则): 这是一个示例说明:
那么,在这些语言中同时使用逻辑运算符和按位运算符有什么具体的理由吗?或者他们只是出于熟悉的原因被包括在内? (我知道您可以在布尔上下文中使用“按位”运算符来避免Java和C#中的短路,但我从来不需要这样的行为,所以我想这可能是一个大部分未使用的特例)
问题内容: 在Python 3中,operator.or_等效于按位,而不是逻辑。为什么没有逻辑运算符? 问题答案: 在与运营商不能表示为,因为它们的功能短路行为: 在这些情况下,永远不会调用。 另一方面,假设的则必须调用,因为函数参数总是在调用函数之前进行求值。