2013-03-05 18 views
10

byłem bawić z najgorszym kodu mógłbym napisać, (w zasadzie próbuje złamać rzeczy) i zauważyłem, że ten kawałek kodu:Zmienne globalne spowolnienie kod

for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
end 
std::cout << x; 

gdzie N jest globalnym biegnie zmienne znacznie wolniej niż:

int N = 10000; 
for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
end 
std::cout << x; 

Co dzieje się ze zmienną globalną, która powoduje, że działa wolniej?

+1

Przypuszczam, że kompilator mógłby podejrzewać, że 'x' zostanie zmodyfikowany wewnątrz funkcji' tan', co uniemożliwi wiele optymalizacji. Nie jestem jednak pewien, czy to się tutaj dzieje. – Pubby

+0

@Pubby Jedyna różnica to opóźnienie N? –

+0

cóż, jeśli 'x' jest lokalne, to oczywiście nie można go zmienić wewnątrz' tan'. Więc deklaracje mają znaczenie. – Pubby

Odpowiedz

7

tl; dr: Wersja lokalna utrzymuje N w rejestrze, wersja globalna nie. Deklaruj stałe ze stałą i będzie szybciej, bez względu na to, jak je zadeklarujesz.


Oto przykładowy kod użyłem:

#include <iostream> 
#include <math.h> 
void first(){ 
    int x=1; 
    int N = 10000; 
    for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
    std::cout << x; 
} 
int N=10000; 
void second(){ 
    int x=1; 
    for(int i = 0; i < N; ++i) 
    tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
    std::cout << x; 
} 
int main(){ 
    first(); 
    second(); 
} 

(nazwany test.cpp).

Aby sprawdzić wygenerowany kod assemblera, uruchomiłem g++ -S test.cpp.

Mam ogromny plik, ale z jakiegoś inteligentnego przeszukiwania (szukałem Tan), znalazłem to, co chciałem:

z first funkcję:

Ltmp2: 
    movl $1, -4(%rbp) 
    movl $10000, -8(%rbp) ; N is here !!! 
    movl $0, -12(%rbp) ;initial value of i is here 
    jmp LBB1_2  ;goto the 'for' code logic 
LBB1_1:    ;the loop is this segment 
    movl -4(%rbp), %eax 
    cvtsi2sd %eax, %xmm0 
    movl -4(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -4(%rbp) 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan   
    callq _tan 
    callq _tan 
    movl -12(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -12(%rbp) 
LBB1_2: 
    movl -12(%rbp), %eax ;value of n kept in register 
    movl -8(%rbp), %ecx 
    cmpl %ecx, %eax ;comparing N and i here 
    jl LBB1_1  ;if less, then go into loop code 
    movl -4(%rbp), %eax 

Druga funkcja:

Ltmp13: 
    movl $1, -4(%rbp) ;i 
    movl $0, -8(%rbp) 
    jmp LBB5_2 
LBB5_1:    ;loop is here 
    movl -4(%rbp), %eax 
    cvtsi2sd %eax, %xmm0 
    movl -4(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -4(%rbp) 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    callq _tan 
    movl -8(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -8(%rbp) 
LBB5_2: 
    movl _N(%rip), %eax ;loading N from globals at every iteration, instead of keeping it in a register 
    movl -8(%rbp), %ecx 

Więc z kodu asemblera widać (lub nie), że w wersji lokalnej, N jest trzymany w rejestrze podczas całego obliczania, podczas gdy w wersji globalnej, N jest ponownie czytane od globalnej w każdej iteracji.

Wyobrażam sobie, że główny powód, dla którego tak się dzieje, dotyczy rzeczy takich jak wątkowanie, kompilator nie może być pewien, że N nie jest modyfikowany.

jeśli dodać const do deklaracji N (const int N=10000), to będzie jeszcze szybciej niż lokalną wersją choć:

movl -8(%rbp), %eax 
    addl $1, %eax 
    movl %eax, -8(%rbp) 
LBB5_2: 
    movl -8(%rbp), %eax 
    cmpl $9999, %eax ;9999 used instead of 10000 for some reason I do not know 
    jle LBB5_1 

N otrzymuje stałą.

+0

Co otrzymasz, jeśli włączysz optymalizację podczas kompilacji? – Mankarse

+4

W procesie decyzyjnym kompilatora wątki nie występują, ponieważ warunki wyścigu są niezdefiniowane. 'N' jest ładowany w każdej iteracji, ponieważ kompilator nie może być pewien, że' tan' nie modyfikuje 'N'. – Mankarse

+0

To wygląda na poważną porażkę optymalizacyjną: Zmodyfikowana lokalna zmienna jak "N" niemodyfikowana może być traktowana jako niezmodyfikowana po inicjalizacji, a zatem zastąpiona stałą, nawet bez 'const'. – Yakk

7

Nie można zoptymalizować wersji globalnej, aby umieścić ją w rejestrze.

+0

, ponieważ musi być dostępny z różnych zakresów. – Krishnabhadra

+0

@Krishnabhadra: Nie w tym przypadku. Rzecz w tym, że kompilator nie wie, że nikt inny go nie użyje. –

+0

Zastanawiam się, czy król optymalizacji, ICC, poradził sobie z tym – hanshenrik

5

Zakładam, że optymalizator nie zna zawartości funkcji tan podczas kompilowania powyższego kodu.

Np. To, co tan nie jest znane - wszystko, co wie, to nałożyć rzeczy na stos, przeskoczyć na jakiś adres, a następnie posprzątać stos.

W przypadku zmiennej globalnej kompilator nie wie, co tan ma do N. W lokalnym przypadku nie ma żadnych "luźnych" wskaźników ani odniesień do N, które można legalnie uzyskać za pomocą tan: więc kompilator wie, jakie wartości przyjmie wartość N.

Kompilator może spłaszczyć pętlę - od dowolnego miejsca (jeden blok płaski z 10000 linii), częściowo (100 pętli długości, każda z 100 linii) lub w ogóle (długość 10000 pętli po 1 linii każda), lub coś pomiędzy.

Kompilator wie dużo więcej, gdy zmienne są lokalne, ponieważ gdy są globalne, mają bardzo małą wiedzę o tym, jak się zmieniają lub kto je czyta. Tak niewiele można założyć.

Zabawnie, dlatego tak trudno ludziom zrozumieć globale.

7

Zrobiłem mały eksperyment z pytaniem i odpowiedzią @rtpg,

eksperymentowanie z pytaniem

W pliku main1.h zmienna globalna N

int N = 10000; 

Następnie w pliku main1.c, 1000 obliczeń sytuacji:

#include <stdio.h> 
#include "sys/time.h" 
#include "math.h" 
#include "main1.h" 



extern int N; 

int main(){ 

     int k = 0; 
     timeval static_start, static_stop; 
     int x = 0; 

     int y = 0; 
     timeval start, stop; 
     int M = 10000; 

     while(k <= 1000){ 

       gettimeofday(&static_start, NULL); 
       for (int i=0; i<N; ++i){ 
         tan(tan(tan(tan(tan(tan(tan(tan(x++)))))))); 
       } 
       gettimeofday(&static_stop, NULL); 

       gettimeofday(&start, NULL); 
       for (int j=0; j<M; ++j){ 
         tan(tan(tan(tan(tan(tan(tan(tan(y++)))))))); 
       } 
       gettimeofday(&stop, NULL); 

       int first_interval = static_stop.tv_usec - static_start.tv_usec; 
       int last_interval = stop.tv_usec - start.tv_usec; 

       if(first_interval >=0 && last_interval >= 0){ 
         printf("%d, %d\n", first_interval, last_interval); 
       } 

       k++; 
     } 

     return 0; 
} 

Wyniki przedstawiono histogram obserwacji (częstotliwość/mikrosekundy):

the histogram for the comparison output time in both methods czerwone pola są non zmienna globalna podstawie zakończone pętli (N), a przezroczysty zielony M zakończony podstawie pętli (nie globalny).

Istnieją dowody na to, że podejrzewamy, że zewnętrzna globalna varialbe jest nieco powolna.

eksperymentowanie z odpowiedzią Powód @rtpg jest bardzo silny. W tym sensie zmienna globalna może być wolniejsza.

Speed of accessing local vs. global variables in gcc/g++ at different optimization levels

Aby przetestować tę przesłankę Używam zmienną globalną rejestru do testowania wydajności. To był mój main1.h z globalnej zmiennej

int N asm ("myN") = 10000; 

Wyniki nowy histogramu:

Results with register global variable

zawarcia istnieją poprawić wydajność, gdy zmienna globalna jest w rejestrze. Nie ma problemu z "globalną" lub "lokalną" zmienną. Wydajność zależy od dostępu do zmiennej.

0

Myślę, że to może być powód: Ponieważ zmienne globalne są przechowywane w pamięci sterty, Twój kod musi mieć dostęp do pamięci sterty za każdym razem. Może być z powodu powyższego kodu przyczyny działa wolno.

+0

zmienne globalne są przechowywane w segmencie DANE STOSOWANIA, a nie w stercie. – niko