[英]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
不是編譯時常量,則調用通用函數如果這里的目標只是優化,而不是在編譯時上下文中啟用使用,您可以向編譯器提供有關您的意圖的提示:
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)
在這方面相當不錯。
編譯器可以做你想做的事嗎?
編譯器可以創建所謂的“函數克隆”,它可以執行您想要的操作。 克隆函數是用於常量傳播的函數的副本,也就是使用常量參數調用的函數的結果程序集。 我發現關於此功能的文檔很少,因此是否要依賴它取決於您。
編譯器可以完全內聯此函數,這可能會使您的問題成為非問題(您可以通過在標頭中內聯定義它、使用 lto 和/或使用編譯器特定屬性(如__attribute__((always_inline))
)來幫助它)
現在,我不是在鼓吹讓編譯器完成它的工作。 盡管這些時候編譯器的優化非常出色,並且經驗法則是信任優化器,但在某些情況下您需要手動干預。 我只是說要意識到並考慮到它。 哦,和往常一樣衡量,衡量,衡量性能時,不要使用“我覺得我需要優化”的直覺。
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
你的版本沒問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.