请考虑以下代码:
// Class definition
class myclass
{
public:
constexpr myclass() noexcept: _value{0}, _option{true} {}
constexpr myclass(int value) noexcept: _value{value}, _option{true} {}
constexpr myclass(int value, bool option) noexcept: _value{value}, _option{option} {}
constexpr int get_value() const noexcept {return _value;}
constexpr int get_option() const noexcept {return _option;}
private:
int _value;
bool _option;
};
// Some function that should be super-optimized
int f(myclass x, myclass y)
{
if (x.get_option() && y.get_option()) {
return x.get_value() + y.get_value();
} else {
return x.get_value() * y.get_value();
}
}
我的问题如下:在这种模式中,编译器通常能够在编译时知道该选项时避免测试,例如,使用f(a, b)
调用a
时{ {1}}整数(在这种情况下调用隐式单参数构造函数,b
总是为真)?当我说"一般"时,我指的是复杂的真实世界计划,但option
在两个f
上被调用。
答案 0 :(得分:1)
简短的回答是"取决于"。这取决于很多东西,包括代码的复杂性,使用的编译器等。
一般来说,常量传播(换句话说,"将作为常量传递给函数的内容转换为常量本身"对于编译器来说并不是一件非常困难的事情.Clang / LLVM这样做在编译过程中很早就有了单独的类,我们知道的值是常量"和#34;我们在生成LLVM-IR时不知道"的值(&#34) 34;中间表示",从源代码构建的代码层,它不代表实际的机器代码。)其他编译器也将有类似的结构,通过使用IR,并通过跟踪常量分离非 - 恒定值。
所以,假设编译器可以"跟随"代码(例如f
和f
的调用位于不同的源文件中,它不太可能得到优化)。
当然,如果您想确定您的特定编译器对您的特定代码所做的事情,您将必须检查编译器生成的代码。
// Class definition
class myclass
{
public:
constexpr myclass() noexcept: _value{0}, _option{true} {}
constexpr myclass(int value) noexcept: _value{value}, _option{true} {}
constexpr myclass(int value, bool option) noexcept: _value{value}, _option{option} {}
constexpr int get_value() const noexcept {return _value;}
constexpr int get_option() const noexcept {return _option;}
private:
int _value;
bool _option;
};
// Some function that should be super-optimized
int f(myclass x, myclass y)
{
if (x.get_option() && y.get_option()) {
return x.get_value() + y.get_value();
} else {
return x.get_value() * y.get_value();
}
}
int main()
{
myclass a;
myclass b(1);
myclass c(2, false);
int x = f(a, b);
int y = f(b, c);
return x + y;
}
这将生成与以下内容相同的代码:
int main()
{
return 3;
}
但是,如果我们将代码更改为:
#include "myclass.h"
extern int f(myclass x, myclass y);
int main()
{
myclass a;
myclass b(1);
myclass c(2, false);
int x = f(a, b);
int y = f(b, c);
return x + y;
}
并在单独的文件中声明f
(-O2
优化),结果代码为
define i32 @_Z1f7myclassS_(i64 %x.coerce, i64 %y.coerce) #0 {
entry:
%x.sroa.0.0.extract.trunc = trunc i64 %x.coerce to i32
%y.sroa.0.0.extract.trunc = trunc i64 %y.coerce to i32
%conv.i = and i64 %x.coerce, 1095216660480
%tobool = icmp eq i64 %conv.i, 0
%conv.i12 = and i64 %y.coerce, 1095216660480
%tobool2 = icmp eq i64 %conv.i12, 0
%or.cond = or i1 %tobool, %tobool2
%add = add nsw i32 %y.sroa.0.0.extract.trunc, %x.sroa.0.0.extract.trunc
%mul = mul nsw i32 %y.sroa.0.0.extract.trunc, %x.sroa.0.0.extract.trunc
%retval.0 = select i1 %or.cond, i32 %mul, i32 %add
ret i32 %retval.0
}
和主要:
define i32 @main() #0 {
entry:
%call = tail call i32 @_Z1f7myclassS_(i64 4294967296, i64 4294967297)
%call4 = tail call i32 @_Z1f7myclassS_(i64 4294967297, i64 2)
%add = add nsw i32 %call4, %call
ret i32 %add
}
如您所见,f
的参数转换为两个64位整数,option
的值存储在64位值的上半部分。函数f
然后将64位值分成两部分,并根据值决定是否返回乘法或加法的结果。