Eksperymentowanie z optymalizacji połączeń ogon (TCO), natknąłem się na następujący ciekawy przykład:Dlaczego gcc przeprowadza optymalizację połączeń końcowych dla jednej wersji, ale nie dla drugiej?
unsigned long long int fac1(unsigned long long int n){
if (n==0)
return 1;
return n*fac1(n-1);
}
faktycznie, byłem pod wrażeniem, że gcc was able wykonać TCO tutaj (z -O2
flagą), ponieważ nie jest to, że prosta:
fac1(unsigned long long):
testq %rdi, %rdi
movl $1, %eax
je .L4
.L3:
imulq %rdi, %rax
subq $1, %rdi
jne .L3
rep ret
.L4:
rep ret
jednak po zmianie rodzaju powrotnej z unsigned long long int
do unsigned int
gcc nie był w stanie wykonać tlo:
unsigned int fac2(unsigned long long int n){
if (n==0)
return 1;
return n*fac2(n-1);
}
możemy jasno zobaczyć wywołanie rekurencyjne w resulting assembly:
fac2(unsigned long long):
testq %rdi, %rdi
jne .L16
movl $1, %eax
ret
.L16:
pushq %rbx
movq %rdi, %rbx
leaq -1(%rdi), %rdi
call fac2(unsigned long long)
imull %ebx, %eax
popq %rbx
ret
W pierwszej chwili odrzucił to jako straconą optymalizacji, ale teraz nie jestem pewien, bo clang isn't able aby wykonać tę optymalizację, a także . Może więc są subtelności języka, którego nie jestem świadomy, które uniemożliwiają tę optymalizację.
Dlaczego gcc nie przeprowadza optymalizacji wywołań końcowych dla funkcji fac2
, ale tylko dla fac1
?
Jest dla mnie jasne, że w drugiej wersji wynik musi być zaniżony. Oczywiście to jedyna różnica. Ale dlaczego miałby to być problem i zapobiegać Tlo?
Na przykład, jeśli mogę pomóc kompilator i przepisać mojej funkcji jako klasyczny ogon-rekursji (który powinien produkować identyczne wyniki do wersji fac2
):
unsigned int tlo_fac(unsigned long long int n, unsigned long long int cur){
if (n==0)
return cur;
return tlo_fac(n-1, n*cur);
}
unsigned int fac(unsigned long long int n){
return tlo_fac(n,1);
}
dostaję tlo zoptymalizowanej wersji, która jest identical to fac1
(wysoka 32bit są allowed to contain garbage, więc imulq
mogą być stosowane po inline):
fac(unsigned long long):
testq %rdi, %rdi
movl $1, %eax
je .L10
.L11:
imulq %rdi, %rax
subq $1, %rdi
jne .L11
.L10:
rep ret
może być fakt, że odlewa się konieczne w 'powrotu n * fac2 (n-1);' po wywołaniu 'fact2 (n-1) ' –
@GauravSehgal, ale dlaczego ten rzutowałby zapobiec Tlo? – ead
Ponieważ rzut zostanie wykonany po przeanalizowaniu głębokości rekursji. Jeśli zmienisz parametr na "fact2 (unisnged int n)", otrzymasz optymalizację. –