2011-10-25 16 views
1
  • 假設僅當bState爲true時,才需要將某個值綁定到某個對象的某個 數據塊。當bState 爲假時,它不是必需的,但它也不妨礙。

以下哪段代碼會更有效率,爲什麼?狀態檢查總是一個有效的事情嗎?

(編輯:更新,現在國家爲對象的成員)

const int x;  
int i; 
int iToBind; 
Classname pObject[x]; 

for (; i < x; ++i) { 
if (pObject[i].bState) { 
     pObject[i].somedatamember = iToBind; 
    } 
} 

對戰:

for (; i < x; ++i) { 
    pObject[i].somedatamember = iToBind; 
} 
+0

我會用後者,但爲什麼不只是運行這兩個版本,看看哪一個更快? –

+1

我猜你已經簡化了你的代碼,但是如你所寫的,你可以在循環之外移動'if(bState)'測試。 – Chowlett

+4

一如既往,答案是:如果你真的關心,*簡介*。可能不會有明顯的差異 - 但這只是我個人的預測。同樣,只有分析才能說明。 – delnan

回答

3

我會說後者肯定會更快。第一個版本具有雙向內存訪問,後者具有單向內存訪問。

在這個版本:

for (; i < x; ++i) { 
    if (pObject[x].bState) { 
    pObject[x].somedatamember = iToBind; 
    } 
} 

存在if語句作爲CPU必須等待數據期間擺攤從內存中讀取。內存讀取的速度取決於數據所在的位置。距離CPU越遠需要的時間越長:L1(最快),L2,L3,Ram,磁盤(最慢)。

在這個版本:

for (; i < x; ++i) { 
    pObject[x].somedatamember = iToBind; 
} 

只有寫入內存。寫入內存不會停止CPU。

除了內存訪問時間,後一個循環在循環內沒有條件跳轉。條件循環是一個很大的開銷,特別是如果採取/未採取的決策是有效的隨機。

+0

關於內存訪問,我認爲在這兩種情況下都是相似的,因爲無論如何都需要將對象加載到CPU緩存中。如果我們進一步瞭解細節,還可以指出第一種形式可能會阻止高速緩存行上的鎖定爭用,因爲CPU內核不需要讀取的獨佔訪問權限? –

+0

@MatthieuM。這聽起來很漂亮!但是,你能解釋一下嗎? :p – xcrypt

+0

@MattieuM:在第一種情況下,整個對象未加載,更重要的是,加載的數據僅由內存IO子系統使用,而不是由CPU內核使用。第二種形式可以完全繞過緩存。 – Skizz

0

爲了什麼我可以告訴bState沒有在循環中首先改變片段,所以你可以把if以外,這顯然更有效率。

1

你有沒有聽說過Loop Invariant Code Motion

這是來自編譯器的優化傳遞,儘可能將代碼移出循環主體。

例如,給定下面的代碼:

#include <stdio.h> 
#include <stdlib.h> 

int main(int argc, char **argv) { 
    for (int i = 0; i < argc; ++i) { 
    if (argc < 100) { 
     printf("%d\n", atoi(argv[1])); 
    } 
    } 
} 

鏘生成以下IR:

define i32 @main(i32 %argc, i8** nocapture %argv) nounwind { 
    %1 = icmp sgt i32 %argc, 0 
    br i1 %1, label %.lr.ph, label %._crit_edge 

.lr.ph:           ; preds = %0 
    %2 = icmp slt i32 %argc, 100 
    %3 = getelementptr inbounds i8** %argv, i64 1 
    br i1 %2, label %4, label %._crit_edge 

; <label>:4          ; preds = %4, %.lr.ph 
    %i.01.us = phi i32 [ %9, %4 ], [ 0, %.lr.ph ] 
    %5 = load i8** %3, align 8, !tbaa !0 
    %6 = tail call i64 @strtol(i8* nocapture %5, i8** null, i32 10) nounwind 
    %7 = trunc i64 %6 to i32 
    %8 = tail call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %7) nounwind 
    %9 = add nsw i32 %i.01.us, 1 
    %exitcond = icmp eq i32 %9, %argc 
    br i1 %exitcond, label %._crit_edge, label %4 

._crit_edge:          ; preds = %4, %.lr.ph, %0 
    ret i32 0 
} 

可以被翻譯回C:

int main(int argc, char** argv) { 
    if (argc == 0) { return 0; } 

    if (argc >= 100) { return 0; } 

    for (int i = 0; i < argc; ++i) { 
    printf("%d\n", atoi(argv[1])); 
    } 

    return 0; 
} 

結論:不打擾微觀優化,除非一個剖析器顯示它們不像您想象的那麼微小。

編輯:

編輯從根本上改變這個問題(上帝,我恨:P)。LICM不再適用,兩種功能的功能差別很大。

但結論仍然相同。 for循環中的單個if檢查不會改變代碼的基本複雜性(請記住,循環條件也在每次迭代中都進行測試...)。

+0

對不起,這個不好的例子。雖然很好的答案:) 編輯:和抱歉的編輯:p – xcrypt

1

這一切都取決於您已爲該帖子簡化了什麼。如果你添加一個分支只是爲了跳過設置一個變量,那麼你可能沒有獲得任何東西,並且如果分支預測失敗,可能會失去。我會刪除測試。

現在,如果要更新的對象不是簡單的int那麼...一如既往,測量,配置文件,然後根據實際事實而不是直覺做出決定。如果這不是緊密循環的一部分,那麼你甚至都不會注意到這種差異。

0

我會說這實際上取決於上下文。如果 bState在綁定過程中至關重要,那麼每次循環迭代檢查狀態的額外1或2個彙編指令將不得不支付。如果不是,則當x特別大時,省略 if

相關問題