Rozdíl mezi deklarování proměnných před nebo ve smyčce?

hlasů
284

Vždycky jsem přemýšlel, jestli obecně deklarovat Jednorázové proměnnou před smyčce, na rozdíl od opakovaně uvnitř smyčky, nedává (výkon) v tom rozdíl? (Zcela nesmyslné) příklad v Javě:

a) deklarace před smyčkou:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

b) prohlášení (opakovaně) uvnitř smyčky:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

Který z nich je lepší, nebo b ?

Domnívám se, že opakovaná deklarace proměnných (příklad b ) vytváří další režii v teorii , ale kompilátory jsou dost chytří na to, aby na tom nezáleží. Příklad b má tu výhodu, že jsou mnohem kompaktnější a omezit rozsah proměnné tam, kde je použit. Přesto mám tendenci kód podle příklad A .

Edit: Mám zájem především v případě Java.

Položena 02/01/2009 v 17:06
zdroj uživatelem
V jiných jazycích...                            


25 odpovědí

hlasů
237

Co je lepší, nebo b ?

Z hlediska výkonu, měli byste si ji změřit. (A podle mého názoru, pokud můžete změřit rozdíl, kompilátor není moc dobré).

Z hlediska údržby, b je lepší. Deklarovat a inicializovat proměnné na stejném místě, v nejužším možném rozsahu. Nenechávejte zející díru mezi prohlášením a inicializaci a neznečišťují jmenné prostory, které nepotřebujete k.

Odpovězeno 02/01/2009 v 17:18
zdroj uživatelem

hlasů
202

Tak jsem běžel vaše A a B příklady každý 20krát, opakování 100 miliónkrát (JVM - 1.5.0).

A: průměrný čas: 0,074 sek

B: průměrný čas: 0,067 sek

K mému velkému překvapení B byl o něco rychlejší. Tak rychle, jak počítače jsou nyní jeho těžké říct, jestli byste mohli přesně změřit toto. Já bych kód jí způsobem, jak dobře, ale řekl bych, že to opravdu nezáleží.

Odpovězeno 02/01/2009 v 17:25
zdroj uživatelem

hlasů
64

Záleží na jazyku a přesné použití. Například v jazyce C # 1 to dělalo žádný rozdíl. V C # 2, je-li lokální proměnná zachycena anonymní metody (nebo lambda výrazu v C # 3) se může velmi signficant rozdíl.

Příklad:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Výstup:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

Rozdíl je v tom, že všechny akce zachytit stejnou outerproměnnou, ale každý má svůj samostatný innerproměnnou.

Odpovězeno 02/01/2009 v 17:16
zdroj uživatelem

hlasů
35

Níže je to, co jsem napsal a sestavil v .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

To je to, co jsem si z NET Reflektor při CIL je vykreslen zpět do kódu.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Tak jak vypadají úplně stejná po sestavení. V řízených jazyků kód přeměněn CL / byte kódu a v době popravy není převeden do strojového jazyka. Takže ve strojovém jazyce dvojitá ani nemusí být vytvořen v zásobníku. Může to být jen zaregistrovat jako číslo bylo zřejmé, že se jedná o dočasné proměnná pro WriteLinefunkci. Existuje celá pravidla nastavená optimalizace jen pro smyček. Takže průměrný člověk by neměl mít strach o tom, a to zejména v řízených jazycích. Existují případy, kdy můžete optimalizovat správu kódu, například máte-li zřetězit velkého počtu stringů pomocí aplikace právě string a; a+=anotherstring[i]vs použití StringBuilder. Tam je velmi velký rozdíl ve výkonu mezi oběma. Existuje mnoho takových případů, kdy může kompilátor není optimalizovat svůj kód, protože nemůže přijít na to, co má ve větším rozsahu. Ale to může do značné míry optimalizovat základní věci pro vás.

Odpovězeno 13/01/2010 v 12:52
zdroj uživatelem

hlasů
24

Jedná se o gotcha v VB.NET. Visual Basic výsledek nebude inicializovat proměnné v tomto příkladu:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

To bude tisknout 0 poprvé (Visual Basic proměnné mají výchozí hodnoty, když prohlásil,!), Ale ipokaždé, když po tom.

Máte-li přidat = 0, i když dostanete to, co by se dalo očekávat:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...
Odpovězeno 02/01/2009 v 17:20
zdroj uživatelem

hlasů
13

Udělal jsem jednoduchý test:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

proti

for (int i = 0; i < 10; i++) {
    int b = i;
}

Zpracovala jsem tyto kódy GCC - 5.2.0. A pak jsem demontáži main () z těchto dvou kódů, a to je výsledek:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

proti

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Které jsou exaclty stejný výsledek asm. Není důkaz, že tyto dva kódy produkují totéž?

Odpovězeno 10/10/2015 v 14:24
zdroj uživatelem

hlasů
11

Já bych vždy používat (spíše než se spoléhat na kompilátor) a mohla by také přepsat na:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

To stále omezuje intermediateResultna rozsah smyčce, ale není znovu deklarovat při každé iteraci.

Odpovězeno 02/01/2009 v 17:18
zdroj uživatelem

hlasů
11

Je to jazyk závislý - IIRC C # optimalizuje to, takže tam není žádný rozdíl, ale JavaScript (například) bude dělat celý příděl paměti shebang pokaždé.

Odpovězeno 02/01/2009 v 17:11
zdroj uživatelem

hlasů
6

Podle mého názoru, b je lepší strukturu. V poslední hodnota intermediateResult hole kolem poté, co vaše smyčky je dokončena.

Edit: To nedává mnoho rozdílu s hodnotovým typům, ale referenční typy mohou být poněkud těžký. Osobně jsem rád proměnné, které mají být dereferenced co nejdříve po vyčištění a b udělá za vás,

Odpovězeno 02/01/2009 v 17:16
zdroj uživatelem

hlasů
5

Tam je rozdíl v jazyce C #, pokud používáte proměnné v lambda, atd. Ale obecně kompilátor bude v podstatě to samé, za předpokladu, že proměnná se používá pouze v rámci cyklu.

Vzhledem k tomu, že jsou v podstatě stejné: Všimněte si, že verze b je mnohem jasnější pro čtenáře, že proměnná není a nemůže být používán po smyčce. Navíc, verze b je mnohem snadněji přepracován. Je těžší získat tělo smyčky do jeho vlastního způsobu provedení A. Navíc verze b vám zaručí, že neexistuje žádný vedlejší účinek na takové refaktorování.

Z tohoto důvodu, verze a mě obtěžuje k žádnému konci, protože neexistuje žádný přínos pro něj, a to je mnohem těžší uvažovat o kódu ...

Odpovězeno 07/11/2012 v 22:15
zdroj uživatelem

hlasů
5

No, mohl byste vždy vytvořit prostor pro to:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

Tímto způsobem můžete pouze deklarovat proměnnou jednou, a to zemře, když necháte smyčku.

Odpovězeno 23/10/2011 v 13:39
zdroj uživatelem

hlasů
5

Spolupracovník preferuje první formu, říkat to je optimalizace, raději znovu použít prohlášení.

Raději ten druhý (a pokusit se přesvědčit mého spolupracovníka ;-)!) Poté, co četl, že:

  • To snižuje rozsah proměnných, kde jsou potřeba, což je dobrá věc.
  • Java optimalizuje tak, aby se žádný významný rozdíl ve výkonu. IIRC, možná druhá forma je ještě rychlejší.

V každém případě to spadá do kategorie předčasné optimalizace, které se spoléhají na kvalitu překladače a / nebo JVM.

Odpovězeno 02/01/2009 v 18:41
zdroj uživatelem

hlasů
5

Jako obecné pravidlo, prohlašuji své proměnné v nejvnitřnější možný rozsah. Takže, pokud si nejste s použitím intermediateResult mimo smyčku, pak bych jít s B.

Odpovězeno 02/01/2009 v 17:15
zdroj uživatelem

hlasů
5

Mám podezření, že několik překladače mohl optimalizovat tak, aby byl stejný kód, ale rozhodně ne všechny. Takže bych řekl, že jste lépe s bývalý. Jediným důvodem pro posledně je, pokud chcete, aby zajistily, že deklarovaná proměnná se používá pouze v rámci vaší smyčky.

Odpovězeno 02/01/2009 v 17:14
zdroj uživatelem

hlasů
4

Vždycky jsem si myslel, že pokud deklarovat své proměnné uvnitř vašeho smyčky pak budete ztrácet paměť. Máte-li něco takového:

for(;;) {
  Object o = new Object();
}

Pak se objekt musí nejen být vytvořen pro každé iteraci, ale je třeba, aby nové reference přidělené pro každý objekt. Zdá se, že v případě, že garbage collector je pomalý, pak budete mít spoustu klátící odkazy, které je třeba vyčistit.

Nicméně, pokud máte tento:

Object o;
for(;;) {
  o = new Object();
}

Pak budete vytvářet pouze jednu referenci a přiřazením nového objektu na něj pokaždé. Jistě, může to trvat trochu déle, než je jít mimo rozsah, ale pak je tu jen jeden visící reference zabývat.

Odpovězeno 16/01/2010 v 07:01
zdroj uživatelem

hlasů
3

Můj postup je následující:

  • pokud typ proměnné je jednoduchý (int, double, ...) raději variantu b (uvnitř).
    Důvod: omezení rozsahu proměnné.

  • pokud typ proměnné není jednoduchá (nějaký druh classnebo struct) Dávám přednost varianta (venku). Důvod: snížení počtu ctor-dtor hovorů.

Odpovězeno 05/02/2014 v 07:03
zdroj uživatelem

hlasů
3

Myslím, že to závisí na kompilátoru a je těžké dát obecnou odpověď.

Odpovězeno 02/01/2009 v 17:08
zdroj uživatelem

hlasů
1

Z hlediska výkonu, venku je (mnohem) lepší.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

popraven I obě funkce 1 miliardu krát každý. vnější () se 65 milisekund. v závorkách () trvalo 1,5 sekundy.

Odpovězeno 20/04/2015 v 12:29
zdroj uživatelem

hlasů
0

Měl jsem velmi stejnou otázku po dlouhou dobu. Takže jsem testoval ještě jednodušší kus kódu.

Závěr: U takových případech je NO Rozdíl ve výkonnosti.

Vnější pouzdro smyčka

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Uvnitř případě smyčky

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Zkontroloval jsem zkompilovaný soubor na IntelliJ je Decompiler a v obou případech jsem dostal stejný Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

Také jsem rozebrat kód pro případ použití metody uvedené v této odpovědi . Ukážu pouze části relevantní pro odpověď

Vnější pouzdro smyčka

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Uvnitř případě smyčky

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Pokud si dávat pozor, jen Slotpřiřazen ia intermediateResultv LocalVariableTableje vyměněn jako součin pořadí jejich vzhledu. Stejný rozdíl ve slotu se odráží v dalších řádků kódu.

  • Žádné další operace se provádí
  • intermediateResult je stále lokální proměnná v obou případech, takže není čas k rozdílu.

BONUS

Překladače udělat tuny optimalizace, podívejte se na to, co se děje v tomto případě.

Zero práce Případ

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

Zero práce rozložit

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}
Odpovězeno 09/02/2019 v 09:18
zdroj uživatelem

hlasů
0

Zkoušel to samé v Go a oproti výstup kompilátoru pomocí go tool compile -Sse jít 1.9.4

Nulový rozdíl, podle výstupu assembler.

Odpovězeno 19/02/2018 v 05:25
zdroj uživatelem

hlasů
0

To je lepší forma

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) tímto způsobem deklarován jedenkrát jak variabilní, a to každý pro cyklus. 2) postoupení je fatser thean všechny ostatní možnosti. 3) Aby pravidlo DobráPraxe je jakákoliv prohlášení mimo iterace.

Odpovězeno 13/07/2016 v 11:06
zdroj uživatelem

hlasů
0

Testoval jsem pro JS s Node 4.0.0, pokud někdo má zájem. Deklarace mimo smyčku za následek zlepšení výkonu ~ .5 ms v průměru o více než 1000 studií s 100 miliónů iterací smyčky na proces. Takže jsem chtěl říct, jděte do toho a zapsat jej do nejvíce čitelné / udržitelného způsobem, který je B, imo. Já bych dal svůj kód na housle, ale použil jsem modul Node výkonově teď. Zde je kód:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)
Odpovězeno 04/04/2016 v 20:32
zdroj uživatelem

hlasů
0

To je zajímavá otázka. Z mé zkušenosti, že je konečný otázka, aby zvážila, když se diskutovat o této záležitosti za kód:

Existuje nějaký důvod, proč by proměnná musí být globální?

Má smysl pouze deklarovat proměnnou jednou, na celém světě, na rozdíl od mnoha časů na místě, protože je to lepší pro organizování kódu a vyžaduje méně řádků kódu. Nicméně, pokud to jen potřebuje být prohlášena za místně v rámci jedné metodě, tak bych ji inicializovat v této metody, takže je jasné, že proměnná je výlučně relevantní pro danou metodu. Dávejte pozor, abyste volat tuto proměnnou mimo způsobu, ve kterém se inicializuje pokud zvolíte druhou možnost - váš kód nebude vědět, o čem to mluví, a ohlásí chybu.

Také, jako okrajová poznámka, nekopírují místní názvy proměnných mezi různé metody, i když jejich cíle jsou téměř totožné; to prostě dostane matoucí.

Odpovězeno 06/03/2015 v 03:38
zdroj uživatelem

hlasů
0

A) je sázka na jistotu než B) ......... Představte si, že jste při inicializaci strukturu smyčky, spíše než ‚int‘ nebo ‚float‘ Tak co?

jako

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

Ty jsou samozřejmě povinni potýkají s problémy s úniky paměti !. Z tohoto důvodu se domnívám, ‚A‘ je bezpečnější sázka, zatímco ‚B‘ je náchylná k hromadění paměti esp práci v blízkosti zdroj libraries.You můžete zjistit usinng ‚Valgrind‘ nástroj na Linux konkrétně dílčí nástroje ‚Helgrindu‘.

Odpovězeno 11/05/2010 v 15:28
zdroj uživatelem

hlasů
-1

Dokonce i když vím, že můj kompilátor je dost chytrý, nebudu rád spoléhat na něj, a bude používat a) variantu.

The b) varianta mi dává smysl pouze tehdy, když zoufale potřebují, aby se intermediateResult nedostupný po těla smyčky. Ale nedovedu si představit takovou zoufalou situaci, tak jako tak ....

EDIT: Jon Skeet udělal velmi dobrý bod, což ukazuje, že deklarace proměnných uvnitř cyklu může znamenat skutečnou významový rozdíl.

Odpovězeno 02/01/2009 v 17:13
zdroj uživatelem

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more