Bir döngü içinde bir değişkeni bildirmenin herhangi bir yükü var mı? (C ++)


157

Merak ediyorum da böyle bir şey yaparsanız hız veya verimlilik kaybı olur mu?

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

ki int varyüz kez ilan eder . Bana öyle geliyormuş gibi görünüyor, ama emin değilim. bunun yerine bunu yapmak daha pratik / hızlı olur mu:

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

yoksa aynı, hızlı ve verimlilik açısından mı?


7
Açıkça ifade etmek gerekirse, yukarıdaki kod, var'ı yüz kez "bildirmez".
jason

1
@Rabarberski: Başvurulan soru, bir dil belirtmediğinden tam olarak bir kopya değil. Bu soru C ++ 'ya özeldir . Ancak, başvurulan sorunuza gönderilen yanıtlara göre, cevap dile ve muhtemelen derleyiciye bağlıdır.
DavidRR

2
@jason İlk kod parçası 'var' değişkenini yüz kez bildirmiyorsa, neler olduğunu açıklayabilir misiniz? Değişkeni sadece bir kez bildirip 100 kez başlatıyor mu? Döngüdeki her şey 100 kez çalıştırıldığı için kodun değişkeni 100 kez bildirip başlattığını düşünmüştüm. Teşekkürler.
randomUser47534

Yanıtlar:


193

Yerel değişkenler için yığın alanı genellikle fonksiyon kapsamında tahsis edilir. Böylece döngü içinde yığın işaretçisi ayarı gerçekleşmez, sadece 4'e atanır var. Bu nedenle, bu iki parçacık aynı ek yüke sahiptir.


50
En azından kolejde öğretmenlik yapanların bu temel şeyi bilmelerini isterdim. Bir defasında bir döngü içinde bir değişken ilan ederken bana güldü ve bunu yapmama nedeni olarak performansı gösterene kadar neyin yanlış olduğunu merak ediyordum ve ben "WTF !?" gibiydim.
Mehrdad Afshari

18
Hemen yığın alanı hakkında konuşmanız gerektiğinden emin misiniz? Bunun gibi bir değişken, bir kayıtta da olabilir.
toto

3
Böyle bir değişken de olabilir @toto hiçbir yerde - varDeğişken başlatılır ama (ikinci pasajı dışında makul bir optimizasyoncusu sadece tamamen kaldırmak, böylece hiç kullanılmamış ise değişken döngü sonra bir yerlerde kullanılmıştır).
CiaPan

@Mehrdad Afshari döngüdeki bir değişken, yapıcısını yineleme başına bir kez çağırır. DÜZENLEME - Aşağıda bundan bahsettiğinizi görüyorum, ancak kabul edilen cevapta da bahsetmeyi hak ettiğini düşünüyorum.
hoodaticus

105

İlkel tipler ve POD türleri için hiçbir fark yaratmaz. Derleyici, değişken için yığın alanını işlevin başlangıcında ayırır ve işlev her iki durumda da geri döndüğünde onu serbest bırakır.

Önemsiz olmayan kurucuları olan POD dışı sınıf türleri için bir fark yaratacaktır - bu durumda, değişkeni döngünün dışına koymak, kurucuyu ve yıkıcıyı yalnızca bir kez ve atama işlecini her yinelemeyi çağırır. döngü, döngünün her yinelemesi için kurucuyu ve yıkıcıyı çağırır. Sınıfın yapıcısı, yıkıcısı ve atama operatörünün ne yaptığına bağlı olarak, bu istenebilir veya istenmeyebilir.


41
Doğru fikri yanlış sebep. Döngünün dışında değişken. Bir kez inşa edildi, bir kez yok edildi, ancak sıralama operatörü her yinelemeyi uyguladı. Döngünün içinde değişken. Constructe / Desatructor, sıfır atama işlemi dışında her yinelemeyi uyguladı.
Martin York

8
Bu en iyi cevap ama bu yorumlar kafa karıştırıcı. Bir kurucu ile bir atama operatörü çağırmak arasında büyük bir fark vardır.
Andrew Grant

1
Bu ise döngü gövdesi sadece başlatma için, zaten atama yaparsa doğrudur. Ve sadece gövdeden bağımsız / sürekli bir başlatma varsa, optimize edici onu kaldırabilir.
peterchen

7
@Andrew Grant: Neden. Atama operatörü genellikle yapının tmp'ye kopyalanması ve bunu takiben takas (istisnai güvenli olmak üzere) ve ardından destroy tmp olarak tanımlanır. Bu nedenle, görevlendirme operatörü yukarıdaki inşa / yok etme döngüsünden o kadar da farklı değildir. Tipik atama operatörü örneği için stackoverflow.com/questions/255612/… adresine bakın .
Martin York

1
İnşa etme / imha etme pahalıysa, bunların toplam maliyeti, operatörün maliyetinin makul bir üst sınırıdır =. Ancak görev gerçekten daha ucuz olabilir. Ayrıca, bu tartışmayı ints'ten C ++ türlerine genişlettiğimizde, 'var = 4'ü' aynı türden bir değerden değişken atama 'dışında başka bir işlem olarak genelleştirebiliriz.
greggo

68

İkisi de aynıdır ve derleyicinin ne yaptığına bakarak nasıl öğrenebileceğiniz aşağıda açıklanmıştır (optimizasyon yüksek olarak ayarlanmasa bile):

Derleyicinin (gcc 4.0) basit örneklerinize ne yaptığına bakın:

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

Bunlardan iki şey görebilirsiniz: Birincisi, kod her ikisinde de aynıdır.

İkinci olarak, var için depolama alanı döngünün dışında ayrılır:

         subl    $24, %esp

Ve son olarak döngüdeki tek şey atama ve koşul kontrolüdür:

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

Bu, döngüyü tamamen kaldırmadan olabildiğince verimli.


2
"Bu, döngüyü tamamen kaldırmadan olabildiğince verimli" Pek değil. Döngüyü kısmen açmak (her geçişte 4 kez demek) onu önemli ölçüde hızlandıracaktır. Muhtemelen optimize etmenin birçok başka yolu vardır ... ancak çoğu modern derleyici muhtemelen döngü oluşturmanın bir anlamı olmadığını fark edecektir. Eğer 'i' daha sonra kullanılsaydı, basitçe 'i' = 100 olacaktı.
darron

bu, kodun artmış 'i' olarak değiştirildiğini varsayıyor ... tıpkı sonsuza kadar bir döngü olduğu gibi.
darron

Orijinal Gönderi gibi!
Alex Brown

2
Teoriyi kanıtla destekleyen cevapları seviyorum! ASM'nin eşit kodlar olma teorisini desteklediğini görmek güzel. +1
Xavi Montero

1
Aslında sonuçları her sürüm için makine kodunu oluşturarak ürettim. Çalıştırmaya gerek yok.
Alex Brown

13

Derleyici kodu daha iyi optimize edebileceğinden (değişken kapsamını azalttığından), sabit olmadığı sürece, bu günlerde onu döngü içinde bildirmek daha iyidir.

DÜZENLEME: Bu cevap çoğunlukla artık geçersiz. Post-klasik derleyicilerin yükselişiyle birlikte, derleyicinin çözemediği durumlar ender hale geliyor. Hala inşa edebilirim ama çoğu insan inşaatı kötü kod olarak sınıflandırır.


4
Optimizasyonu etkileyeceğinden şüpheliyim - derleyici herhangi bir tür veri akışı analizi yaparsa, döngünün dışında değiştirilmediğini anlayabilir, bu nedenle her iki durumda da aynı optimize edilmiş kodu üretmelidir.
Adam Rosenfield

3
Yine de aynı temp değişken adını kullanan iki farklı döngüye sahip olup olmadığınızı anlamayacaktır.
Joshua

11

Çoğu modern derleyici bunu sizin için optimize eder. Daha okunaklı bulduğum için ilk örneğinizi kullanacağım söyleniyor.


3
Bunu gerçekten bir optimizasyon olarak saymıyorum. Yerel değişkenler oldukları için, yığın alanı sadece fonksiyonun başında tahsis edilir. Performansa zarar verecek gerçek bir "yaratım" yoktur (bir kurucu çağrılmadıkça, ki bu tamamen başka bir hikaye).
Mehrdad Afshari

Haklısınız, "optimizasyon" yanlış kelime ama ben daha iyisi için bir kayıpım.
Andrew Hare

sorun, böyle bir optimize edicinin canlı aralık analizi kullanması ve her iki değişkenin de oldukça ölü olmasıdır.
MSalters

"Derleyici, veri akışı analizini yaptıktan sonra aralarında herhangi bir fark görmeyecek" e ne dersiniz? Kişisel olarak, bir değişkenin kapsamının, verimlilik için değil, açıklık için kullanıldığı yerle sınırlı olmasını tercih ederim.
greggo

9

Yerleşik bir tür için 2 stil arasında büyük olasılıkla hiçbir fark olmayacaktır (muhtemelen üretilen koda kadar).

Bununla birlikte, değişken önemsiz olmayan bir kurucu / yıkıcıya sahip bir sınıfsa, çalışma zamanı maliyetinde büyük bir fark olabilir. Genelde değişkeni döngünün içine dahil ederdim (kapsamı olabildiğince küçük tutmak için), ancak bunun mükemmel bir etkisi olduğu ortaya çıkarsa, sınıf değişkenini döngünün kapsamının dışına taşımaya bakarım. Ancak, ode yolunun anlambilimi değişebileceğinden, bunu yapmak bazı ek analizler gerektirir, bu nedenle bu yalnızca anlambilim izin verirse yapılabilir.

Bir RAII sınıfı bu davranışa ihtiyaç duyabilir. Örneğin, dosya erişimini doğru bir şekilde yönetmek için, dosya erişim ömrünü yöneten bir sınıfın her döngü yinelemesinde oluşturulması ve yok edilmesi gerekebilir.

LockMgrOluşturulduğunda kritik bir bölüm alan ve yok edildiğinde onu serbest bırakan bir sınıfınız olduğunu varsayalım :

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

şundan oldukça farklıdır:

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

    // do stuff...

}

6

Her iki döngü de aynı verime sahiptir. İkisi de sonsuz bir zaman alacak :) Döngülerin içinde i'yi artırmak iyi bir fikir olabilir.


Ah evet, alan verimliliğine değinmeyi unuttum - sorun değil - her ikisi için 2 inç. Programcıların ağaç için ormanı gözden kaçırması bana garip geliyor - sonlanmayan bazı kodlarla ilgili tüm bu öneriler.
Larry Watanabe

Sonlandırmazlarsa sorun değil. İkisi de aranmıyor. :-)
Nosredna

2

Bir keresinde bazı performans testleri yaptım ve şaşırtıcı bir şekilde, vaka 1'in aslında daha hızlı olduğunu gördüm! Sanırım bunun nedeni, döngünün içindeki değişkeni bildirmenin kapsamını daraltması, dolayısıyla daha önce serbest kalması olabilir. Ancak, bu çok eski bir derleyicide uzun zaman önceydi. Modern derleyicilerin farklılıkları ortadan kaldırmak için daha iyi bir iş çıkardığından eminim, ancak değişken kapsamınızı olabildiğince kısa tutmak yine de zarar vermez.


Fark muhtemelen kapsamdaki farktan kaynaklanmaktadır. Kapsam ne kadar küçükse, derleyicinin değişkenin serileştirilmesini ortadan kaldırma olasılığı o kadar yüksektir. Küçük döngü kapsamında, değişken büyük olasılıkla bir yazmacıya yerleştirildi ve yığın çerçevesine kaydedilmedi. Döngüde bir işlev çağırırsanız veya derleyicinin gerçekten nereye işaret ettiğini bilmediği bir göstericiye başvuruda bulunmazsanız, işlev kapsamındaysa döngü değişkenini yayar (işaretçi içerebilir &i).
Patrick Schlüter

Lütfen kurulumunuzu ve sonuçlarınızı gönderin.
jxramos

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

Yukarıdaki kod her zaman 100 10 kez yazdırır, bu da döngü içindeki yerel değişkenin her işlev çağrısı için yalnızca bir kez tahsis edildiği anlamına gelir.


0

Emin olmanın tek yolu onları zamanlamaktır. Ancak, eğer varsa, fark mikroskobik olacaktır, bu nedenle çok büyük bir zamanlama döngüsüne ihtiyacınız olacak.

Daha da önemlisi, birincisi daha iyi stildir çünkü değişken var değişkenini başlatırken diğeri onu başlatılmamış olarak bırakır. Bu ve değişkenlerin mümkün olduğunca kullanım noktalarına yakın tanımlanması gerektiği yönündeki kılavuz, normalde ilk formun tercih edilmesi gerektiği anlamına gelir.


"Emin olmanın tek yolu onları zamanlamaktır." -1 doğru değil. Üzgünüz, ancak başka bir gönderi, üretilen makine dilini karşılaştırarak ve temelde aynı bularak bunun yanlış olduğunu kanıtladı. Genel olarak cevabınızla ilgili bir problemim yok, ama -1'in ne için olduğu yanlış değil mi?
Bill K

Yayılan kodu incelemek kesinlikle faydalıdır ve bunun gibi basit bir durumda yeterli olabilir. Bununla birlikte, daha karmaşık durumlarda, referans konumu gibi sorunlar başını döndürür ve bunlar yalnızca yürütme zamanlamasıyla test edilebilir.

-1

Yalnızca iki değişkenle, derleyici muhtemelen her ikisi için de bir kayıt atayacaktır. Bu kayıtlar zaten orada, bu yüzden bu zaman almıyor. Her iki durumda da 2 yazmaç yazma ve bir yazmaç okuma talimatı vardır.


-2

Bence yanıtların çoğunda dikkate alınması gereken önemli bir nokta eksik: "Açık mı?" Ve tüm tartışmalara göre gerçek şu ki; hayır öyle değil. Çoğu döngü kodunda, verimliliğin hemen hemen sorun olmadığını (bir mars iniş aracını hesaplamadıkça) öneririm, bu yüzden gerçekten tek soru daha mantıklı, okunabilir ve sürdürülebilir görünen şeydir - bu durumda beyan etmenizi tavsiye ederim döngünün önündeki ve dışındaki değişken - bu, onu daha net hale getirir. O zaman sizin gibi insanlar, geçerli olup olmadığını görmek için çevrimiçi olarak kontrol etmekle zaman kaybetmeye bile zahmet etmem.


-6

bu doğru değil, ama ihmal edilebilir ek yükü var.

Muhtemelen yığın üzerinde aynı yerde son bulacak olsalar bile, yine de onu atar. Bu int için yığın üzerinde bellek konumu atayacak ve sonra} sonunda serbest bırakacaktır. Yığın serbest anlamda değil, sp'yi (yığın işaretçisi) 1 oranında hareket ettirecektir. Ve sizin durumunuzda, sadece bir yerel değişkene sahip olduğunu düşünürsek, sadece fp (çerçeve işaretçisi) ve sp'yi eşitleyecektir.

Kısa cevap: NEREDEYSE AYNI ŞEKİLDE ÇALIŞIRSA DİKKAT ETMEYİN.

Ancak yığının nasıl düzenlendiği hakkında daha fazla okumayı deneyin. Lisans okulumun bu konuda oldukça iyi dersleri vardı Daha fazlasını okumak istiyorsanız buraya bakın http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html


Yine, -1 doğru değil. Montajı inceleyen yazıyı okuyun.
Bill K

hayır yanılıyorsun. bu kodla oluşturulan assembler koduna bakın
grobartn
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.