为什么C++编译器无法优化"if(test) --foo"以"foo -= test"?
Why does the C++ compiler fail to optimize "if(test) --foo" to "foo -= test"?
我有一个函数,它求给定整数2的下一个幂。如果整数是2的幂,则返回该整数的幂
非常直接:
char nextpow2if(int a)
{
char foo = char(32 - __builtin_clz(a));
bool ispow2 = !(a & a-1);
if (ispow2) --foo;
return foo;
}
然而,在用-O2编译gcc 6之后,在检查生成的程序集之后,我看到这是在计算foo-1之后用看似无用的指令cmovne
编译的。更糟糕的是,使用gcc5和更早的版本,我在代码中得到了一个实际的jne
分支。
更快的编译方法是编写以下函数:
char nextpow2sub(int a)
{
char foo = char(32 - __builtin_clz(a));
bool ispow2 = !(a & a-1);
return foo - ispow2;
}
此代码被所有编译器正确编译为最短(和最快)可能的汇编,并带有sete
和bool的减法。
为什么编译器不能优化第一个?这看起来是一个很容易识别的情况。为什么gcc 5和更早的版本要将其编译为实际的jne
分支?这两个版本之间是否存在一个我看不到的边缘情况,可能会导致它们的行为不同?
PS:现场演示在这里
编辑:我还没有测试过gcc 6的性能,但在gcc 5中,后者大约快了两倍(至少在综合性能测试中)。这就是我问这个问题的原因我认为这样做的原因可能是bool
通常存储在一个字节内。因此,编译器可能无法安全地假设实际内存恰好等于1。true
/false
检查可能只是与零比较。然而,减法可能是一个不同的故事,有副作用。
参见Ideone上的示例代码:
#include <iostream>
using namespace std;
union charBool
{
unsigned char aChar;
bool aBool;
};
int main()
{
charBool var;
charBool* varMemory = &var;
var.aBool = 65;
std::cout << "a boolean = " << var.aBool << std::endl;
std::cout << "a char = " << var.aChar << std::endl;
std::cout << "varMemory = " << (*(reinterpret_cast<unsigned char*>(varMemory))) << std::endl;
var.aChar = 98; // note: Ideone C++ compiler resolves this to zero, hence bit0 seems to be the only checked
std::cout << "a boolean = " << var.aBool << std::endl;
std::cout << "a char = " << var.aChar << std::endl;
std::cout << "varMemory = " << (*(reinterpret_cast<unsigned char*>(varMemory))) << std::endl;
return 0;
}
结果是:
a boolean = 1
a char =
varMemory =
a boolean = 0
a char = b
varMemory = b
(注意:前两个字符不可打印)
编译器确实可以在不违反标准的情况下执行此优化。但是考虑以下稍微不同的情况:
char nextpow2sub(int a)
{
char foo = char(32 - __builtin_clz(a));
bool ispow2 = !(a & a-1);
return foo - (5 * ispow2);
}
char nextpow2if(int a)
{
char foo = char(32 - __builtin_clz(a));
bool ispow2 = !(a & a-1);
if (ispow2) foo = foo - 5;
return foo;
}
我在这里做的唯一改变是我减去了5而不是1。如果使用gcc 6进行编译。X和比较,您将看到两个函数生成的二进制代码具有相同的大小。我也希望他们俩有或多或少相同的表现。
这表明编译器所使用的优化算法被设计为处理一般情况。也就是说,即使在减去1的情况下,我预计(使用gcc 6.x)在任何支持指令级并行性和寄存器重命名的现代处理器上,性能都会有微小的差异。
这段代码被所有编译器正确编译为最短的(和)最快的)
sete
和bool
的减法组装。
你怎么知道这是最短最快的代码?是的,它更短更快,但你有证据证明这是最短最快的吗?而且,如果不指定特定的体系结构和微体系结构,就不能给出这样的声明。
- 我知道函数调用中存在歧义.有没有办法调用foo()函数
- 如何使用 Google Test 向测试添加元数据 / 如何将数据从 Google Test 发送到 TestEven
- Boost.TEST with CLion: "Test framework quit unexpectedly"
- 仅让特定类'Fabric'构造类'Foo'及其所有子类的实例
- Google Test for OpenCv c++
- 使用 Google Test 对自定义断言函数进行单元测试
- 反转C++ foo(MyClass &) vs foo(const MyClass &)
- CMake 错误 - 目标 foo INTERFACE_SOURCES属性包含在源目录中以前缀为前缀的路径
- 如何实现这个函数 foo.getSize().x.
- Clearing Class Foo with new(pFoo) Foo()
- 柯南,CMake.test()生成XML报告
- 如何在 google test in windows 中管理断言
- google test PrintTo for std::set<std::string>
- 我正在尝试学习如何在 c++ 中传递指针,但出现错误:没有用于调用"test"的匹配函数。我做错了什么?
- 我想知道为什么"std::unique_ptr<int> foo(新 int)"是合法的,因为"std::<int>unique_ptr"要求输入参数类型应该是"int"?
- Xcode Test Navigator 如何在纯C++项目中显示 Google 测试
- 使用 G++ 编译时"Undefined Refrence to Foo"
- XCode 警告"此处需要实例化变量'Singleton:<Foo>:_instance',但没有可用的定义
- foo(void) vs foo(void *)
- 为什么C++编译器无法优化"if(test) --foo"以"foo -= test"?