簡體   English   中英

針對編譯時常量優化的函數

[英]Function optimized for compile-time constant

假設我有一個向量長度計算函數,它有一個額外的inc參數(它告訴相鄰元素之間的距離)。 一個簡單的實現是:

float calcLength(const float *v, int size, int inc) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

現在,可以使用兩種inc參數調用calcLength :在編譯時知道inc時和不知道時。 我想要一個優化的calcLength版本,用於inc常見編譯calcLength (如 1)。

所以,我會有這樣的事情:

template <int C>
struct Constant {
    static constexpr int value() {
        return C;
    }
};

struct Var {
    int v;

    constexpr Var(int p_v) : v(p_v) { }

    constexpr int value() const {
        return v;
    }
};

template <typename INC>
float calcLength(const float *v, int size, INC inc) {
        float l = 0;

        for (int i=0; i<size*inc.value(); i += inc.value()) {
            l += v[i]*v[i];
        }
        return sqrt(l);
    }
}

所以,這可以使用:

calcLength(v, size, Constant<1>()); // inc is a compile-time constant 1 here, calcLength can be vectorized

或者

int inc = <some_value>;
calcLength(v, size, Var(inc)); // inc is a non-compile-time constant here, less possibilities of compiler optimization

我的問題是,是否有可能以某種方式保留原始接口,並根據inc的類型(編譯時常量與否)自動放入Constant / Var

calcLength(v, size, 1); // this should end up calcLength(v, size, Constant<1>());
calcLength(v, size, inc); // this should end up calcLength(v, size, Var(int));

注意:這是一個簡單的例子。 在我的實際問題中,我有幾個函數,比如calcLength ,而且它們很大,我不希望編譯器內聯它們。


注2:我也對不同的方法持開放態度。 基本上,我想有一個解決方案,它滿足這些:

  • 算法被指定一次(很可能在模板函數中)
  • 如果我將1指定為inc ,則實例化一個特殊函數,並且代碼很可能會被矢量化
  • 如果inc不是編譯時常量,則調用通用函數
  • 否則(非 1 編譯時常量):調用哪個函數無關緊要

如果這里的目標只是優化,而不是在編譯時上下文中啟用使用,您可以向編譯器提供有關您的意圖的提示:

static float calcLength_inner(const float *v, int size, int inc) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

float calcLength(const float *v, int size, int inc) {
    if (inc == 1) {
        return calcLength_inner(v, size, inc);  // compiler knows inc == 1 here, and will optimize
    }
    else {
        return calcLength_inner(v, size, inc);
    }
}

從 Godbolt ,你可以看到calcLength_inner已經被實例化了兩次,有和沒有常量傳播。

這是一個 C 技巧(並且在 numpy 中廣泛使用),但是您可以編寫一個簡單的包裝器以使其更易於在 C++ 中使用:

// give the compiler a hint that it can optimize `f` with knowledge of `cond`
template<typename Func>
auto optimize_for(bool cond, Func&& f) {
    if (cond) {
        return std::forward<Func>(f)();
    }
    else {
        return std::forward<Func>(f)();
    }
}

float calcLength(const float *v, int size, int inc) {
    return optimize_for(inc == 1, [&]{
        float l = 0;
        for (int i=0; i<size*inc; i += inc) {
            l += v[i]*v[i];
        }
        return sqrt(l);
    });
}

C ++沒有提供檢測提供的函數參數是否是常量表達式的方法,因此您無法自動區分提供的文字和運行時值。

如果參數必須是一個函數參數,並且你不願意改變它在兩種情況下調用的方式,那么你在這里唯一的杠桿就是參數的類型:你對Constant<1>()建議Constant<1>() vs Var(inc)在這方面相當不錯。

選項 1:相信你的編譯器(也就是什么都不做)

編譯器可以做你想做的事嗎?

編譯器可以創建所謂的“函數克隆”,它可以執行您想要的操作。 克隆函數是用於常量傳播的函數的副本,也就是使用常量參數調用的函數的結果程序集。 我發現關於此功能的文檔很少,因此是否要依賴它取決於您。

編譯器可以完全內聯此函數,這可能會使您的問題成為非問題(您可以通過在標頭中內聯定義它、使用 lto 和/或使用編譯器特定屬性(如__attribute__((always_inline)) )來幫助它)

現在,我不是在鼓吹讓編譯器完成它的工作。 盡管這些時候編譯器的優化非常出色,並且經驗法則是信任優化器,但在某些情況下您需要手動干預。 我只是說要意識到並考慮到它。 哦,和往常一樣衡量,衡量,衡量性能時,不要使用“我覺得我需要優化”的直覺。

選項 2:兩個重載

float calcLength(const float *v, int size, int inc) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

template <int Inc>
float calcLength(const float *v, int size) {
    float l = 0;

    for (int i=0; i<size*inc; i += inc) {
        l += v[i]*v[i];
    }
    return sqrt(l);
}

這里的缺點是代碼重復,ofc。 在調用站點也很少需要注意:

calcLength(v, size, inc); // ok
calcLength<1>(v, size);   // ok
calcLength(v, size, 1);   // nope

選項 3:您的版本

你的版本沒問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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