簡體   English   中英

在循環中聲明變量是否有任何開銷? (C++)

[英]Is there any overhead to declaring a variable within a loop? (C++)

我只是想知道如果你做這樣的事情是否會降低速度或效率:

int i = 0;
while(i < 100)
{
    int var = 4;
    i++;
}

它聲明了int var一百次。 在我看來好像會有,但我不確定。 這樣做會更實用/更快嗎:

int i = 0;
int var;
while(i < 100)
{
    var = 4;
    i++;
}

或者它們在速度和效率方面是否相同?

局部變量的堆棧空間通常在函數范圍內分配。 所以循環內部不會發生堆棧指針調整,只是將 4 分配給var 因此,這兩個片段具有相同的開銷。

對於原始類型和 POD 類型,它沒有區別。 在這兩種情況下,編譯器都會在函數開始時為變量分配堆棧空間,並在函數返回時釋放它。

對於具有非平凡構造函數的非 POD 類類型,它會有所作為——在這種情況下,將變量放在循環外只會調用一次構造函數和析構函數,每次迭代只會調用賦值運算符,而將它放在循環之外循環將為循環的每次迭代調用構造函數和析構函數。 根據類的構造函數、析構函數和賦值運算符的作用,這可能是也可能不是可取的。

它們都是相同的,以下是您可以通過查看編譯器的功能(即使沒有將優化設置為高)來找出答案的方法:

看看編譯器 (gcc 4.0) 對你的簡單示例做了什么:

1.c:

main(){ int var; while(int i < 100) { var = 4; } }

gcc -S 1.c

1.s:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $24, %esp
    movl    $0, -16(%ebp)
    jmp L2
L3:
    movl    $4, -12(%ebp)
L2:
    cmpl    $99, -16(%ebp)
    jle L3
    leave
    ret

2.c

main() { while(int i < 100) { int var = 4; } }

gcc -S 2.c

2.s:

_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        movl    $0, -16(%ebp)
        jmp     L2
L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3
        leave
        ret

從這些中,您可以看到兩件事:首先,兩者的代碼相同。

其次,var 的存儲在循環外分配:

         subl    $24, %esp

最后,循環中唯一的事情是賦值和條件檢查:

L3:
        movl    $4, -12(%ebp)
L2:
        cmpl    $99, -16(%ebp)
        jle     L3

在不完全刪除循環的情況下,這與您的效率差不多。

現在最好在循環中聲明它,除非它是一個常量,因為編譯器將能夠更好地優化代碼(減少變量范圍)。

編輯:這個答案現在大部分已經過時了。 隨着后經典編譯器的興起,編譯器無法解決的情況越來越少。 我仍然可以構造它們,但大多數人會將構造歸類為不良代碼。

大多數現代編譯器都會為您優化這一點。 話雖如此,我會使用您的第一個示例,因為我發現它更具可讀性。

對於內置類型,兩種樣式之間可能沒有區別(可能直接到生成的代碼)。

但是,如果變量是一個具有非平凡構造函數/析構函數的類,那么運行時成本很可能會有很大差異。 我通常會將變量范圍限定在循環內(以保持范圍盡可能小),但如果結果證明這對性能有影響,我會考慮將類變量移到循環范圍之外。 然而,這樣做需要一些額外的分析,因為 ode 路徑的語義可能會改變,所以這只能在語義允許的情況下完成。

RAII 類可能需要這種行為。 例如,可能需要在每次循環迭代中創建和銷毀管理文件訪問生命周期的類,以正確管理文件訪問。

假設您有一個LockMgr類,它在構造時獲取臨界區並在銷毀時釋放它:

while (i< 100) {
    LockMgr lock( myCriticalSection); // acquires a critical section at start of
                                      //    each loop iteration

    // do stuff...

}   // critical section is released at end of each loop iteration

與以下完全不同:

LockMgr lock( myCriticalSection);
while (i< 100) {

    // do stuff...

}

兩個循環具有相同的效率。 它們都將花費無限量的時間:) 在循環內增加 i 可能是一個好主意。

我曾經運行過一些性能測試,令我驚訝的是,發現案例 1 實際上更快! 我想這可能是因為在循環內聲明變量會減少它的范圍,所以它更早被釋放。 然而,那是很久以前的事了,在一個非常老的編譯器上。 我確信現代編譯器在優化差異方面做得更好,但保持變量范圍盡可能短仍然沒有壞處。

#include <stdio.h>
int main()
{
    for(int i = 0; i < 10; i++)
    {
        int test;
        if(i == 0)
            test = 100;
        printf("%d\n", test);
    }
}

上面的代碼總是打印 100 10 次,這意味着每個函數調用只分配一次循環內部的局部變量。

唯一確定的方法是給它們計時。 但是差異,如果有的話,將是微觀的,所以你需要一個強大的定時循環。

更重要的是,第一個是更好的風格,因為它初始化了變量 var,而另一個讓它未初始化。 這一點以及應該在盡可能靠近其使用點的地方定義變量的准則意味着通常應該首選第一種形式。

只有兩個變量,編譯器可能會為兩者分配一個寄存器。 這些寄存器無論如何都在那里,所以這不需要時間。 在任何一種情況下,都有 2 個寄存器寫入和一個寄存器讀取指令。

我認為大多數答案都忽略了一個要考慮的主要問題:“是否清楚”,顯然所有討論中的事實是; 不它不是。 我建議在大多數循環代碼中,效率幾乎不是問題(除非你計算火星着陸器),所以真正唯一的問題是看起來更合理、可讀和可維護 - 在這種情況下,我建議聲明循環前面和外部的變量 - 這只是讓它更清晰。 然后像你和我這樣的人甚至不會浪費時間在線檢查它是否有效。

這不是真的有開銷但是它可以忽略的開銷。

即使它們可能最終會在堆棧上的同一個位置它仍然分配它。 它將在堆棧上為該 int 分配內存位置,然后在 } 末尾釋放它。 不是在堆自由意義上,它會將 sp(堆棧指針)移動 1。在您的情況下,考慮到它只有一個局部變量,它只會簡單地等同於 fp(幀指針)和 sp

簡短的回答是:不要在意任何一種工作方式幾乎相同。

但是嘗試閱讀更多關於堆棧是如何組織的。 我的本科學校在這方面有很好的講座如果你想閱讀更多在這里查看http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html

暫無
暫無

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

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