簡體   English   中英

在編譯時已知成員的情況下,在函數分支中刪除

[英]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();
    }
}

我的問題如下:在這種模式下,編譯器通常可以避免在編譯時知道該選項時進行測試,例如,用ab整數調用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM