[英]In function branch removal when a member is known at compile-time
考慮以下代碼:
// 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();
}
}
我的問題如下:在這種模式下,編譯器通常可以避免在編譯時知道該選項時進行測試,例如,用a
和b
整數調用f(a, b)
(在這種情況下,隱式單參數構造函數被調用,並且該option
始終為true)? 當我說“一般”時,我的意思是在復雜的實際程序中,但是在兩個int
上調用f
。
簡短的答案是“依賴”。 這取決於事物的數量,包括代碼的復雜性,使用的編譯器等。
通常,常量傳播(換句話說,“將作為常量傳遞給函數的東西轉換為常量本身”)對於編譯器來說並不是一件很困難的事情。Clang/ LLVM很早就在編譯過程中通過單獨生成LLVM-IR時,“我們知道的值是常量”和“我們不知道的值”的類(“中間表示”,即由源代碼構建的代碼層,不代表實際的機器代碼)其他編譯器也將具有類似的構造,這兩種方式都可以通過使用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位值分為兩部分,並根據該值決定是返回乘法還是加法結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.