2013-03-05 6 views
10

avrei dovuto vedermela con il codice peggiore che potrei scrivere, (fondamentalmente cercando di rompere le cose) e ho notato che questo pezzo di codice:Le variabili globali rallentare codice

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

dove n è un corre delle variabili globali significativamente più lento:

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

Cosa succede con una variabile globale che lo rende più lento?

+1

Suppongo che il compilatore possa sospettare che 'x' venga modificato all'interno della funzione' tan' che impedisce molta ottimizzazione. Non sono sicuro che questo avvenga qui. – Pubby

+0

@Pubby L'unica differenza è tuttavia la decelerazione N? –

+0

bene, se 'x' è locale quindi ovviamente non può essere modificato all'interno di' tan'. Quindi le dichiarazioni contano. – Pubby

risposta

7

tl; dr: la versione locale mantiene N in un registro, la versione globale no. Dichiarate le costanti con const e sarà più veloce, indipendentemente da come lo dichiarate.


Ecco il codice di esempio che ho usato:

#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(); 
} 

(chiamato test.cpp).

Per osservare il codice assembler generato ho eseguito g++ -S test.cpp.

ho ottenuto un file enorme, ma con un po 'intelligente ricerca (ho cercato per tan), ho trovato quello che volevo:

dalla funzione first:

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 

seconda funzione:

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 

Quindi dal codice dell'assemblatore è possibile vedere (o meno) che, nella versione locale, N viene tenuto in un registro durante l'intero calcolo, mentre nella versione globale, N è riletto dal globale ad ogni iterazione.

Immagino che il motivo principale per cui questo accade sia per cose come il threading, il compilatore non può essere sicuro che N non sia modificato.

se si aggiunge una const alla dichiarazione di N (const int N=10000), sarà ancora più veloce rispetto alla versione locale però:

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 è sostituita da una costante.

+0

Cosa ottieni se attivi l'ottimizzazione durante la compilazione? – Mankarse

+4

Il threading non è incluso nel processo decisionale del compilatore, perché le condizioni di competizione sono un comportamento indefinito. 'N' è caricato ogni iterazione perché il compilatore non può essere sicuro che' tan' non modifichi 'N'. – Mankarse

+0

Sembra un serio errore di ottimizzazione: una variabile locale non modificata e "non esposta" come 'N' può essere considerata come mai modificata dopo l'inizializzazione, e quindi sostituita con una costante, anche senza' const'. Le variabili globali – Yakk

7

La versione globale non può essere ottimizzata per inserirla in un registro.

+0

perché deve essere accessibile da diversi ambiti. – Krishnabhadra

+0

@Krishnabhadra: non in questo caso. Il fatto è che il compilatore non sa che nessun altro lo utilizzerà. –

+0

chiedo se il re delle ottimizzazioni, ICC, potrebbe gestire questo – hanshenrik

5

Suppongo che l'ottimizzatore non conosca il contenuto della funzione tan durante la compilazione del codice precedente.

Cioè, ciò che tan fa è sconosciuto - tutto ciò che sa è di roba in pila, saltare a qualche indirizzo, quindi pulire lo stack in seguito.

Nel caso di variabile globale, il compilatore non sa cosa fa tan a N. Nel caso locale, non ci sono puntatori "allentati" o riferimenti a tan legittimi che possono essere ottenuti da tan in modo legittimo: in questo modo il compilatore sa quali valori assume N.

Il compilatore può appiattire il ciclo, ovunque da un intero (un blocco piatto di 10000 linee), parzialmente (100 loop di lunghezza, ciascuno con 100 linee) o non del tutto (lunghezza di 10000 loop di 1 riga ciascuno), o qualsiasi altra via di mezzo.

Il compilatore sa molto di più quando le variabili sono locali, perché quando sono globali ha pochissime conoscenze su come cambiano, o chi le legge. Quindi poche ipotesi possono essere fatte.

In modo divertente, questo è anche il motivo per cui è difficile per gli esseri umani ragionare sui globali.

7

ho fatto un piccolo esperimento con la domanda e la risposta di @rtpg,

a sperimentare con la questione

Nel file main1.h la variabile globale N

int N = 10000; 

Quindi nel file main1.c, 1000 calcoli della situazione:

#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; 
} 

I risultati sono mostrati nell'istogramma follow (frequenza/microsecondi):

the histogram for the comparison output time in both methods riquadri rossi sono la variabile non globale basata terminato ciclo (N), e il verde trasparente M Ended basati ciclo for (non globale).

Ci sono prove per sospettare che il varialbe globale esterno sia un po 'lento.

sperimentazione con la risposta Il motivo di @ rtpg è molto forte. In questo senso, una variabile globale potrebbe essere più lenta.

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

Per testare questa premessa io uso una variabile globale registro per testare le prestazioni. Questa era la mia main1.h con variabile globale

int N asm ("myN") = 10000; 

Il nuovo istogramma risultati:

Results with register global variable

conclusione ci sono prestazioni a migliorare quando la variabile globale è nel registro. Non esiste un problema di variabile "globale" o "locale". La performance dipende dall'accesso alla variabile.

0

penso che questo potrebbe essere un motivo: Poiché le variabili globali sono memorizzate nella memoria heap, il codice deve accedere ogni volta alla memoria heap. Potrebbe essere a causa di un codice di errore superiore a quello lento.

+0

sono memorizzate nel segmento DATI di STACK e non in heap. – niko