Zastanawiam się tylko, czy nastąpiłaby jakakolwiek utrata szybkości lub wydajności, gdybyś zrobił coś takiego:
int i = 0;
while(i < 100)
{
int var = 4;
i++;
}
który deklaruje int var
sto razy. Wydaje mi się, że tak będzie, ale nie jestem pewien. czy zamiast tego byłoby to bardziej praktyczne / szybsze:
int i = 0;
int var;
while(i < 100)
{
var = 4;
i++;
}
czy też są takie same, pod względem szybkości i wydajności?
Odpowiedzi:
Przestrzeń stosu dla zmiennych lokalnych jest zwykle przydzielana w zakresie funkcji. Wewnątrz pętli nie ma więc możliwości dostosowania wskaźnika stosu, wystarczy przypisać 4 do
var
. Dlatego te dwa fragmenty mają ten sam narzut.źródło
var
zmienna jest zainicjowana, ale nigdy nie jest używana, więc rozsądny optymalizator może ją całkowicie usunąć (z wyjątkiem drugiego fragmentu, jeśli zmienna została użyta gdzieś po pętli).W przypadku typów pierwotnych i POD nie ma to znaczenia. Kompilator przydzieli miejsce na stosie zmiennej na początku funkcji i zwolni je, gdy funkcja zwróci w obu przypadkach.
W przypadku typów klas innych niż POD, które mają nietrywialne konstruktory, będzie to miało znaczenie - w takim przypadku umieszczenie zmiennej poza pętlą spowoduje wywołanie konstruktora i destruktora tylko raz oraz operatora przypisania w każdej iteracji, podczas gdy umieszczenie jej wewnątrz loop wywoła konstruktor i destruktor dla każdej iteracji pętli. W zależności od tego, co robi konstruktor, destruktor i operator przypisania klasy, może to być pożądane lub nie.
źródło
Oba są takie same, a oto jak możesz się tego dowiedzieć, patrząc na to, co robi kompilator (nawet bez ustawienia optymalizacji):
Spójrz, co kompilator (gcc 4.0) robi z Twoimi prostymi przykładami:
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
Z tego widać dwie rzeczy: po pierwsze, kod jest taki sam w obu.
Po drugie, pamięć dla var jest przydzielana poza pętlą:
subl $24, %esp
I wreszcie jedyną rzeczą w pętli jest przypisanie i sprawdzenie stanu:
L3: movl $4, -12(%ebp) L2: cmpl $99, -16(%ebp) jle L3
Co jest tak wydajne, jak tylko możesz, bez całkowitego usuwania pętli.
źródło
Obecnie lepiej jest zadeklarować to wewnątrz pętli, chyba że jest to stała, ponieważ kompilator będzie mógł lepiej zoptymalizować kod (zmniejszając zakres zmiennych).
EDYCJA: Ta odpowiedź jest teraz w większości nieaktualna. Wraz z pojawieniem się postklasycznych kompilatorów przypadki, w których kompilator nie może tego zrozumieć, stają się rzadkie. Nadal mogę je skonstruować, ale większość ludzi zaklasyfikowałaby konstrukcję jako zły kod.
źródło
Większość nowoczesnych kompilatorów zoptymalizuje to dla Ciebie. Mając to na uwadze, użyłbym twojego pierwszego przykładu, ponieważ uważam go za bardziej czytelny.
źródło
W przypadku typu wbudowanego prawdopodobnie nie będzie różnicy między dwoma stylami (prawdopodobnie aż do wygenerowanego kodu).
Jeśli jednak zmienna jest klasą z nietrywialnym konstruktorem / destruktorem, może wystąpić duża różnica w kosztach czasu wykonania. Generalnie ograniczałbym zmienną do wewnątrz pętli (aby zakres był jak najmniejszy), ale jeśli okaże się, że ma to wpływ na wydajność, chciałbym przenieść zmienną klasy poza zakres pętli. Jednak zrobienie tego wymaga dodatkowej analizy, ponieważ semantyka ścieżki ody może się zmienić, więc można to zrobić tylko wtedy, gdy pozwala na to sematyka.
Klasa RAII może potrzebować takiego zachowania. Na przykład klasa zarządzająca okresem istnienia dostępu do plików może wymagać utworzenia i zniszczenia przy każdej iteracji pętli, aby prawidłowo zarządzać dostępem do plików.
Załóżmy, że masz
LockMgr
klasę, która uzyskuje sekcję krytyczną podczas tworzenia i zwalnia ją po zniszczeniu: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
różni się od:
LockMgr lock( myCriticalSection); while (i< 100) { // do stuff... }
źródło
Obie pętle mają taką samą wydajność. Oba zajmie nieskończoną ilość czasu :) Dobrym pomysłem może być inkrementacja i wewnątrz pętli.
źródło
Kiedyś przeprowadziłem kilka testów wydajności i ku mojemu zdziwieniu stwierdziłem, że przypadek 1 był faktycznie szybszy! Przypuszczam, że może to być spowodowane tym, że zadeklarowanie zmiennej wewnątrz pętli zmniejsza jej zakres, więc wcześniej zostanie zwolniona. Jednak to było dawno temu na bardzo starym kompilatorze. Jestem pewien, że nowoczesne kompilatory lepiej radzą sobie z optymalizacją różnic, ale nadal nie zaszkodzi utrzymywać jak najkrótszy zakres zmiennych.
źródło
&i
).#include <stdio.h> int main() { for(int i = 0; i < 10; i++) { int test; if(i == 0) test = 100; printf("%d\n", test); } }
Powyższy kod zawsze wypisuje 100 10 razy, co oznacza, że zmienna lokalna wewnątrz pętli jest przydzielana tylko raz na każde wywołanie funkcji.
źródło
Jedynym sposobem, aby mieć pewność, jest zmierzenie czasu. Ale różnica, jeśli taka istnieje, będzie mikroskopijna, więc będziesz potrzebować potężnej pętli czasowej.
Co więcej, pierwsza jest lepszym stylem, ponieważ inicjalizuje zmienną var, podczas gdy druga pozostawia ją niezainicjowaną. To oraz wskazówka, że należy definiować zmienne jak najbliżej miejsca ich użycia, oznacza, że zwykle preferowana jest pierwsza forma.
źródło
Mając tylko dwie zmienne, kompilator prawdopodobnie przypisze rejestr dla obu. Te rejestry i tak tam są, więc to nie zajmuje czasu. W obu przypadkach są 2 instrukcje zapisu rejestru i jedna instrukcja odczytu rejestru.
źródło
Myślę, że w większości odpowiedzi brakuje ważnej kwestii do rozważenia, która brzmi: „Czy to jasne” i oczywiście w całej dyskusji jest taki fakt; nie, nie jest. Sugerowałbym, że w większości kodu pętli wydajność praktycznie nie stanowi problemu (chyba że obliczasz dla lądownika marsjańskiego), więc tak naprawdę jedynym pytaniem jest, co wygląda na bardziej rozsądne, czytelne i możliwe do utrzymania - w tym przypadku zalecałbym zadeklarowanie zmienna z przodu i poza pętlą - to po prostu sprawia, że jest jaśniejszy. Wtedy ludzie tacy jak ty i ja nie zawracalibyśmy sobie głowy marnowaniem czasu na sprawdzanie online, czy jest ważny, czy nie.
źródło
to nieprawda, istnieje narzut, ale jego zaniedbany narzut.
Mimo że prawdopodobnie skończą w tym samym miejscu na stosie. Nadal je przypisuje. Przypisze miejsce w pamięci na stosie dla tego int, a następnie zwolni je na końcu}. Nie w sensie bez sterty, przesunie sp (wskaźnik stosu) o 1. A w twoim przypadku, biorąc pod uwagę tylko jedną zmienną lokalną, po prostu zrówna fp (wskaźnik ramki) i sp
Krótka odpowiedź brzmiałaby: NIE DBAJ O INNY SPOSÓB DZIAŁA PRAWIE TAKIE SAME.
Ale spróbuj przeczytać więcej na temat organizacji stosu. Moja szkoła miała całkiem niezłe wykłady na ten temat. Jeśli chcesz przeczytać więcej, zajrzyj tutaj http://www.cs.utk.edu/~plank/plank/classes/cs360/360/notes/Assembler1/lecture.html
źródło