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łą.
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
@Pubby Jedyna różnica to opóźnienie N? –
cóż, jeśli 'x' jest lokalne, to oczywiście nie można go zmienić wewnątrz' tan'. Więc deklaracje mają znaczenie. – Pubby