Zawsze zastanawiałem się, czy, ogólnie rzecz biorąc, zadeklarowanie zmiennej wyrzucającej przed pętlą, w przeciwieństwie do wielokrotnego powtarzania się w pętli, robi jakąkolwiek różnicę (wydajność)? (Zupełnie bez sensu) przykład w Javie:
a) deklaracja przed pętlą:
double intermediateResult;
for(int i=0; i < 1000; i++){
intermediateResult = i;
System.out.println(intermediateResult);
}
b) deklaracja (wielokrotnie) pętli wewnętrznej:
for(int i=0; i < 1000; i++){
double intermediateResult = i;
System.out.println(intermediateResult);
}
Który jest lepszy, a lub b ?
Podejrzewam, że powtarzanie deklaracji zmiennych (przykład b ) powoduje większy narzut teoretycznie , ale kompilatory są na tyle inteligentne, że nie ma to znaczenia. Przykład b ma tę zaletę, że jest bardziej zwarty i ogranicza zakres zmiennej do miejsca, w którym jest używana. Mimo to mam tendencję do kodowania zgodnie z przykładem a .
Edycja: Szczególnie interesuje mnie sprawa Java.
java
performance
loops
variables
initialization
Rabarberski
źródło
źródło
Odpowiedzi:
Który jest lepszy a lub b ?
Z punktu widzenia wydajności musisz to zmierzyć. (I moim zdaniem, jeśli można zmierzyć różnicę, kompilator nie jest zbyt dobry).
Z punktu widzenia konserwacji b jest lepsze. Deklaruj i inicjalizuj zmienne w tym samym miejscu, w możliwie najwęższym zakresie. Nie zostawiaj luki między deklaracją a inicjalizacją i nie zanieczyszczaj przestrzeni nazw, których nie potrzebujesz.
źródło
Cóż, sprawdziłem przykłady A i B po 20 razy, zapętlając 100 milionów razy. (JVM - 1.5.0)
Odp .: średni czas wykonania: 0,074 sek
B: średni czas wykonania: 0,067 sek
Ku mojemu zaskoczeniu B był nieco szybszy. Tak szybko, jak komputery są teraz trudne do stwierdzenia, czy można to dokładnie zmierzyć. Kodowałbym to również w sposób A, ale powiedziałbym, że to tak naprawdę nie ma znaczenia.
źródło
To zależy od języka i dokładnego użycia. Na przykład w C # 1 nie miało znaczenia. W C # 2, jeśli zmienna lokalna jest przechwycona przez anonimową metodę (lub wyrażenie lambda w C # 3), może to zrobić bardzo znaczącą różnicę.
Przykład:
Wynik:
Różnica polega na tym, że wszystkie akcje przechwytują tę samą
outer
zmienną, ale każda ma własną oddzielnąinner
zmienną.źródło
Outer
być 9?Oto, co napisałem i skompilowałem w .NET.
To właśnie otrzymuję z .NET Reflector, gdy CIL jest ponownie renderowany do kodu.
Oba wyglądają dokładnie tak samo po kompilacji. W językach zarządzanych kod jest konwertowany na kod CL / bajtowy, aw momencie wykonania jest konwertowany na język maszynowy. Tak więc w języku maszynowym podwójne nie może nawet zostać utworzone na stosie. Może to być tylko rejestr, ponieważ kod odzwierciedla, że jest to zmienna tymczasowa dla . Istnieje bardzo duża różnica w wydajności między nimi. Istnieje wiele takich przypadków, w których kompilator nie może zoptymalizować kodu, ponieważ nie może zrozumieć, co jest zamierzone w większym zakresie. Ale może zoptymalizować dla Ciebie podstawowe rzeczy.
WriteLine
funkcji. Istnieje cały zestaw zasad optymalizacji tylko dla pętli. Przeciętny facet nie powinien się tym martwić, zwłaszcza w językach zarządzanych. Zdarzają się sytuacje, w których można zoptymalizować zarządzanie kodem, na przykład, jeśli trzeba połączyć dużą liczbę ciągów za pomocą funkcji juststring a; a+=anotherstring[i]
vs usingStringBuilder
źródło
To jest gotcha w VB.NET. Wynik Visual Basic nie zainicjuje ponownie zmiennej w tym przykładzie:
Spowoduje to wydrukowanie 0 po raz pierwszy (zmienne Visual Basic mają wartości domyślne, gdy są deklarowane!), Ale
i
każdym razem po tym.Jeśli dodasz
= 0
, otrzymasz, czego możesz się spodziewać:źródło
Zrobiłem prosty test:
vs
Skompilowałem te kody za pomocą gcc - 5.2.0. A potem zdemontowałem main () z tych dwóch kodów i oto wynik:
1º:
vs
2º
Które są dokładnie takie same jak wynik. nie jest dowodem na to, że oba kody produkują to samo?
źródło
Jest to zależne od języka - IIRC C # optymalizuje to, więc nie ma żadnej różnicy, ale JavaScript (na przykład) wykona cały szereg alokacji pamięci za każdym razem.
źródło
Zawsze używałbym A (zamiast polegać na kompilatorze) i mógłbym również przepisać do:
To nadal ogranicza
intermediateResult
zakres pętli, ale nie zmienia stanu podczas każdej iteracji.źródło
Moim zdaniem b jest lepszą strukturą. W a ostatnia wartość parametru intermedResult pozostaje w pobliżu po zakończeniu pętli.
Edycja: Nie ma to większego znaczenia w przypadku typów wartości, ale typy referencyjne mogą być nieco ciężkie. Osobiście podoba mi się, że zmienne mają być usuwane jak najszybciej w celu wyczyszczenia, i b robi to za ciebie,
źródło
sticks around after your loop is finished
- chociaż nie ma to znaczenia w języku takim jak Python, gdzie powiązane nazwy trzymają się do końca funkcji.my
słowem kluczowym), C # i Java, żeby wymienić 5, których używałem.Podejrzewam, że kilka kompilatorów może zoptymalizować oba pod kątem tego samego kodu, ale na pewno nie wszystkie. Więc powiedziałbym, że lepiej ci z tym pierwszym. Jedynym powodem jest to, że chcesz się upewnić, że deklarowana zmienna jest używana tylko w pętli.
źródło
Zasadniczo deklaruję moje zmienne w możliwie najbardziej wewnętrznym zakresie. Więc jeśli nie używasz pośredniego wyniku poza pętlą, wybrałbym B.
źródło
Współpracownik woli pierwszą formę, mówiąc, że jest to optymalizacja, woląc ponownie użyć deklaracji.
Wolę drugi (i przekonać mojego współpracownika! ;-)), po przeczytaniu:
W każdym razie należy do kategorii przedwczesnej optymalizacji, która polega na jakości kompilatora i / lub JVM.
źródło
Istnieje różnica w języku C #, jeśli używasz zmiennej w lambda itp. Ale ogólnie kompilator zasadniczo robi to samo, zakładając, że zmienna jest używana tylko w pętli.
Biorąc pod uwagę, że są one w zasadzie takie same: Zauważ, że wersja b sprawia, że czytelnicy stają się bardziej oczywiste, że zmienna nie jest i nie może być używana po pętli. Ponadto wersję b można znacznie łatwiej refaktoryzować. Trudniej jest wyodrębnić ciało pętli do własnej metody w wersji a.Co więcej, wersja b zapewnia, że nie wystąpi żaden efekt uboczny takiego refaktoryzacji.
Dlatego wersja denerwuje mnie bez końca, ponieważ nie ma z tego żadnych korzyści i znacznie utrudnia rozumowanie na temat kodu ...
źródło
Cóż, zawsze możesz zrobić na to cel:
W ten sposób deklarujesz zmienną tylko raz, a ona umrze, gdy opuścisz pętlę.
źródło
Zawsze myślałem, że jeśli zadeklarujesz zmienne w pętli, marnujesz pamięć. Jeśli masz coś takiego:
Następnie nie tylko trzeba utworzyć obiekt dla każdej iteracji, ale dla każdego obiektu musi być przypisane nowe odwołanie. Wygląda na to, że jeśli śmieciarz jest powolny, będziesz mieć kilka zwisających referencji, które należy oczyścić.
Jeśli jednak masz to:
Następnie tworzysz tylko jedno odniesienie i za każdym razem przypisujesz do niego nowy obiekt. Jasne, może upłynąć trochę dłużej, zanim wyjdzie poza zakres, ale wtedy jest tylko jedno wiszące odniesienie do rozwiązania.
źródło
Myślę, że to zależy od kompilatora i trudno jest udzielić ogólnej odpowiedzi.
źródło
Moja praktyka jest następująca:
jeśli typ zmiennej jest prosty (int, double, ...) , preferuję wariant b (wewnątrz).
Powód: ograniczenie zakresu zmiennej.
jeśli typ zmiennej nie jest prosty (jakiś rodzaj
class
lubstruct
) , wolę wariant a (na zewnątrz).Powód: zmniejszenie liczby połączeń ctor-dtor.
źródło
Z punktu widzenia wydajności, na zewnątrz jest (znacznie) lepsze.
Obie funkcje wykonałem 1 miliard razy każda. outside () zajęło 65 milisekund. inside () zajęło 1,5 sekundy.
źródło
Testowałem pod kątem JS z Node 4.0.0, jeśli ktoś jest zainteresowany. Zadeklarowanie poza pętlą spowodowało poprawę wydajności o ~ 0,5 ms średnio ponad 1000 prób przy 100 milionach iteracji pętli na próbę. Więc powiem: naprzód i napisz to w najbardziej czytelny / łatwy do utrzymania sposób, którym jest B, imo. Umieściłem mój kod w skrzypcach, ale użyłem modułu Node o wydajności. Oto kod:
źródło
A) jest bezpiecznym zakładem niż B) ......... Wyobraź sobie, że jeśli inicjujesz strukturę w pętli zamiast „int” lub „float”, to co?
lubić
Na pewno napotkasz problemy z wyciekiem pamięci! Dlatego uważam, że „A” jest bezpieczniejszy, podczas gdy „B” jest podatny na gromadzenie pamięci, szczególnie w przypadku pracy w pobliżu bibliotek źródłowych. Możesz sprawdzić użycie narzędzia „Valgrind” w systemie Linux, a konkretnie podrzędnego narzędzia „Helgrind”.
źródło
To interesujące pytanie. Z mojego doświadczenia wynika, że podczas debaty na ten temat nad kodem należy rozważyć ostateczne pytanie:
Czy jest jakiś powód, dla którego zmienna musiałaby być globalna?
Sensowne jest deklarowanie zmiennej tylko raz, globalnie, a nie wiele razy lokalnie, ponieważ lepiej organizuje kod i wymaga mniej wierszy kodu. Jeśli jednak trzeba go zadeklarować lokalnie w ramach jednej metody, zainicjowałbym go w tej metodzie, aby było jasne, że zmienna dotyczy wyłącznie tej metody. Uważaj, aby nie wywoływać tej zmiennej poza metodą, w której jest ona inicjowana, jeśli wybierzesz tę drugą opcję - twój kod nie będzie wiedział o czym mówisz i zgłosi błąd.
Na marginesie: nie powielaj nazw lokalnych zmiennych między różnymi metodami, nawet jeśli ich cele są prawie identyczne; robi się po prostu mylące.
źródło
to jest lepsza forma
1) w ten sposób zadeklarowano raz zmienną, a nie każdą dla cyklu. 2) zadanie jest grubsze niż wszystkie inne opcje. 3) Zatem zasadą najlepszych praktyk jest każde oświadczenie poza iteracją.
źródło
Próbowałem tego samego w Go i porównałem dane wyjściowe kompilatora za pomocą
go tool compile -S
pomocą go 1.9.4Zero różnicy, jak na wyjściu asemblera.
źródło
Długo miałem to samo pytanie. Przetestowałem więc jeszcze prostszy fragment kodu.
Wniosek: Dla takich przypadkach istnieje NO różnica wydajności.
Obudowa z zewnętrzną pętlą
Obudowa z wewnętrzną pętlą
Sprawdziłem skompilowany plik w dekompilatorze IntelliJ i w obu przypadkach otrzymałem to samo
Test.class
Zdemontowałem również kod dla obu przypadków, używając metody podanej w tej odpowiedzi . Pokażę tylko części istotne dla odpowiedzi
Obudowa z zewnętrzną pętlą
Obudowa z wewnętrzną pętlą
Jeśli zwrócisz szczególną uwagę, tylko
Slot
przypisane doi
iintermediateResult
wLocalVariableTable
zostaną zamienione jako wynik ich kolejności pojawiania się. Ta sama różnica w gnieździe znajduje odzwierciedlenie w innych wierszach kodu.intermediateResult
nadal jest zmienną lokalną w obu przypadkach, więc nie ma różnicy w czasie dostępu.PREMIA
Kompilatory wykonują mnóstwo optymalizacji, spójrz na to, co dzieje się w tym przypadku.
Zerowy przypadek roboczy
Dekompilacja zerowej pracy
źródło
Nawet jeśli wiem, że mój kompilator jest wystarczająco inteligentny, nie chcę na nim polegać i użyję wariantu a).
Wariant b) ma dla mnie sens tylko wtedy, gdy rozpaczliwie potrzebujesz zrobić wynik pośredni niedostępny po treści pętli. Ale i tak nie wyobrażam sobie takiej desperackiej sytuacji ...
EDYCJA: Jon Skeet zrobił bardzo dobry punkt, pokazując, że deklaracja zmiennej w pętli może mieć znaczącą różnicę semantyczną.
źródło