我有一段代碼要求執行速度高于其他任何東西。通過使用high_resolution_clock()
fromstd::chrono
我發現這個 switch() 到 if-else() 梯形圖占用了我 70% 以上的執行時間。有什么辦法可以加快這個速度嗎?
我在編譯期間使用優化gcc
。-O3
我研究了一個類似的問題:If else 梯形優化,但我不能使用 return 陳述句,因為它會退出我不能的外回圈。
switch(RPL_OPTION) {
case 0:
for(int k = 0; k < WINDOW_SIZE; k ) {
if(ans[k] >= upper_th) {
//Increasing flag counter
flag_count ;
//Adding the filtered value to the output vector
filtered_output.push_back(ans[k]);
flag_output.push_back(1);
} else if(ans[k] < lower_th) {
//Increasing flag counter
flag_count ;
//Adding the filtered value to the output vector
filtered_output.push_back(ans[k]);
flag_output.push_back(1);
} else {
//Adding the filtered value to the output vector
filtered_output.push_back(ans[k]);
flag_output.push_back(0);
}
}
break;
case 1:
for(int k = 0; k < WINDOW_SIZE; k ) {
if(ans[k] >= upper_th) {
//Increasing flag counter
flag_count ;
//Adding the filtered value to the output vector
filtered_output.push_back(RPL_CONST);
flag_output.push_back(1);
} else if(ans[k] < lower_th) {
//Increasing flag counter
flag_count ;
//Adding the filtered value to the output vector
filtered_output.push_back(RPL_CONST);
flag_output.push_back(1);
} else {
//Adding the filtered value to the output vector
filtered_output.push_back(ans[k]);
flag_output.push_back(0);
}
}
break;
case 2:
for(int k = 0; k < WINDOW_SIZE; k ) {
if(ans[k] >= upper_th) {
//Increasing flag counter
flag_count ;
//Adding the filtered value to the output vector
filtered_output.push_back(upper_th);
flag_output.push_back(1);
} else if(ans[k] < lower_th) {
//Increasing flag counter
flag_count ;
//Adding the filtered value to the output vector
filtered_output.push_back(lower_th);
flag_output.push_back(1);
} else {
//Adding the filtered value to the output vector
filtered_output.push_back(ans[k]);
flag_output.push_back(0);
}
}
break;
case 3:
//Generating a gaussian noise distribution with 0 mean and 1 std deviation
default_random_engine generator(time(0));
normal_distribution<float> dist(0,1);
for(int k = 0; k < WINDOW_SIZE; k ) {
if(ans[k] >= upper_th) {
//Increasing flag counter
flag_count ;
//Calling a random sample from the distribution and calculating a noise value
filtered_output.push_back(dist(generator)*sigma);
flag_output.push_back(1);
continue;
} else if(ans[k] < lower_th) {
//Increasing flag counter
flag_count ;
//Calling a random sample from the distribution and calculating a noise value
filtered_output.push_back(dist(generator)*sigma);
flag_output.push_back(1);
continue;
} else {
//Adding the filtered value to the output vector
filtered_output.push_back(ans[k]);
flag_output.push_back(0);
}
}
break;
}
uj5u.com熱心網友回復:
我幾乎 98% 確定 if-else 階梯不是問題。
對我來說,具有大量重新分配和資料復制的std::vector
s(或您使用的任何容器)push_back
函式是優化的主要候選者。
請使用該reserve
功能預先分配所需的記憶體。
然后移出所有不變的東西,比如
default_random_engine generator(time(0));
normal_distribution<float> dist(0,1);
但是沒有更多的示例代碼,很難判斷。
分析器將為您提供更好的結果。定時器功能在這里不會有太大幫助。
uj5u.com熱心網友回復:
想到的一些優化:
vector.push_back()
或者emplace_back()
,即使使用reserve()
,也會影響性能,因為沒有編譯器能夠向量化代碼。我們可以使用普通的 C 指標,或者只是預分配。如果重復呼叫此代碼,則在最后一種情況下生成隨機引擎和分發可能會產生巨大的成本。我們可以把它從代碼中提升出來。請注意,這也將避免使用低解析度時間函式的重復初始化問題。
這可能是不必要的,但稍微重寫代碼可能會允許更多的編譯器優化,特別是通過將事情變成條件移動指令并減少分支的數量。
/* TODO: We have better ways of initializing generators but that is
* unrelated to its performance
* I'm lazy and turn this into a static variable. Better use a
* different pattern (like up in the stack somewhere)
* but you get the idea
*/
static default_random_engine generator(time(0));
static normal_distribution<float> dist(0,1);
std::size_t output_pos = filtered_output.size();
filtered_output.resize(output_pos WINDOW_SIZE);
flag_output.resize(output_pos WINDOW_SIZE);
switch(RPL_OPTION) {
case 0:
for(int k = 0; k < WINDOW_SIZE; k ) {
auto ansk = ans[k];
int flag = (ansk >= upper_th) | (ansk < lower_th);
flag_count = flag;
filtered_output[output_pos k] = ansk;
flag_output[output_pos k] = flag;
}
break;
case 1:
for(int k = 0; k < WINDOW_SIZE; k ) {
auto ansk = ans[k];
int flag = (ansk >= upper_th) | (ansk < lower_th);
flag_count = flag;
// written carefully to help compiler turning this into a CMOV
auto filtered = flag ? RPL_CONST : ansk;
filtered_output[output_pos k] = filtered;
flag_output[output_pos k] = flag;
}
break;
case 2:
for(int k = 0; k < WINDOW_SIZE; k ) {
auto ansk = ans[k];
int flag = (ansk >= upper_th) | (ansk < lower_th);
flag_count = flag;
auto filtered = ansk < lower_th ? lower_th : ansk;
filtered = ansk >= upper_th ? upper_th : filtered;
filtered_output[output_pos k] = filtered;
flag_output[output_pos k] = flag;
}
break;
case 3:
for(int k = 0; k < WINDOW_SIZE; k ) {
// optimized under the assumption that flag is usually 1
auto ansk = ans[k];
auto random = dist(generator) * sigma;
int flag = (ansk >= upper_th) | (ansk < lower_th);
auto filtered = flag ? random : ansk;
filtered_output[output_pos k] = filtered;
flag_output[output_pos k] = flag;
}
break;
}
uj5u.com熱心網友回復:
首先要注意的是你push_back
在向量上。該代碼顯示沒有呼叫,reserve
因此這將隨著向量的不斷增長而調整其大小。這可能比回圈中的其他任何東西都貴。
接下來的事情涉及“if-else-ladder”:
如果梯子有問題,你真的分析過嗎?分支只有在錯誤預測時才會變得昂貴。也許分支預測器在您的輸入上作業得很好?假設這個 switch 陳述句被執行了很多次,一種幫助它的方法是對輸入進行排序。然后 if-else-ladder 不會每次都隨機跳躍,而是在切換到新案例之前以相同的方式重復多次。但這僅在回圈運行多次或排序成本將抵消任何改進時才有幫助。
如果開關重復多次,您可以將輸入分成 3 組一次,用于梯形圖中的 3 個選項,并在沒有任何 if-else-ladder 的情況下處理每個組。
仔細查看代碼,我發現 if-else-ladder 中的前 2 個案例是相同的(案例 2 除外)。因此,您可以結合測驗使其成為一個簡單的“if-else”:
if ((ans[k] >= upper_th) || (ans[k] < lower_th))
現在由于惰性評估,這將產生與以前相同的代碼。但你可以做得更好:
if ((ans[k] >= upper_th) | (ans[k] < lower_th))
現在這兩個部分都得到了評估,因為沒有對 | 進行惰性評估。除了編譯器是人為的愚蠢之外,無論如何都可能只是進行惰性評估。此時,您正在與優化器作斗爭。一些編譯器會將其優化為 2 個分支,有些則將其保留為一個。
您可以在那里使用以下技巧:
static auto fn[] = {
[&]() { code for first choice; };
[&]() { code for second choice; };
};
fn[((ans[k] >= upper_th) | (ans[k] < lower_th))]();
通過將您的條件if
轉換為計算陣列的索引,可以規避產生 2 個分支的編譯器優化。希望。至少直到下一次編譯器更新。:)
在與優化器作斗爭時,每次更新編譯器時都必須重新檢查解決方案。
而對于案例 2,代碼中的差異只是 push_back 的值。如果您使用,這可以變成大多數架構而不是分支的條件移動
(ans[k] >= upper_th) ? upper_th : lower_th;
對于 push_back。
uj5u.com熱心網友回復:
我有一段代碼要求執行速度高于其他任何東西
這表明了一種不明顯的方法。從信號處理的角度來看,代碼模式看起來已經足夠熟悉了,因此WINDOW_SIZE
可能很重要。在這種情況下,將 AVX2 與打包比較一起使用是有意義的。
簡而言之,您將一個完整的 AVX2 暫存器打包為輸入,使用兩個 AVX2 暫存器來存盤下限和上限閾值的副本,然后發出兩個比較。這為您提供了兩個輸出,其中每個值為 0 或~0
.
因此,您的標志數由兩個暫存器或兩個暫存器確定。已經很想計算標志了,但這被認為是一個緩慢的“水平添加”。最好在另一個 AVX 暫存器中跟蹤這一點,并在最后做一個水平添加。
更新filtered_output
取決于具體情況,但對于 1 和 2,您也可以使用 AVX。case 0:
當然應該只是直接復制到filtered_output
,if
陳述句之外。
uj5u.com熱心網友回復:
首先,由于基準預測,排序ans可能是一個好主意。但這主要取決于ans的大小。其次,如果您使用的是 c 20,您可以查看 [LIKELY] 和 [UNLIKELY] 關鍵字。如果您可以選擇主要選擇哪個陳述句或相反,您可以輕松使用它們。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/493991.html