Próbuję dowiedzieć się o wektoryzacji, studiując prosty kod C skompilowany w gcc z optymalizacją -O3. Dokładniej, jak dobrze kompilatory wektoryzują. Jest to osobista podróż do możliwości sprawdzenia wydajności z bardziej złożonymi obliczeniami. Rozumiem, że konwencjonalna mądrość polega na tym, że kompilatory są lepsze od ludzi, ale ja nigdy nie uważam takiej mądrości za pewnik.Nadmiarowość kodu zespołu w zoptymalizowanym kodzie kodu
W moim pierwszym prostym teście znajduję niektóre z wyborów, które są dość dziwne i, szczerze mówiąc, rażąco niedbałe pod względem optymalizacji. Jestem skłonny założyć, że jest coś, co kompilator jest celowy i wie coś o procesorze (Intel i5-2557M w tym przypadku), którego nie mam. Ale potrzebuję potwierdzenia od dobrze poinformowanych ludzi.
Simple kod testowy (segment) jest:
int i;
float a[100];
for (i=0;i<100;i++) a[i]= (float) i*i;
Wynikowy kod zespół (segmentów), który odpowiada pętli for następująco:
.L6: ; loop starts here
movdqa xmm0, xmm1 ; copy packed integers in xmm1 to xmm0
.L3:
movdqa xmm1, xmm0 ; wait, what!? WHY!? this is redundant.
cvtdq2ps xmm0, xmm0 ; convert integers to float
add rax, 16 ; increment memory pointer for next iteration
mulps xmm0, xmm0 ; pack square all integers in xmm0
paddd xmm1, xmm2 ; pack increment all integers by 4
movaps XMMWORD PTR [rax-16], xmm0 ; store result
cmp rax, rdx ; test loop termination
jne .L6
I zrozumieć, że wszystkie etapy i obliczeniowo, wszystko ma sens. Co nie rozumiem, choć jest gcc Wybierając się na włączenie do pętli iteracyjnej krok, aby załadować XMM1 z xmm0 zaraz po xmm0 załadowano XMM1. tj.
.L6
movdqa xmm0, xmm1 ; loop starts here
.L3
movdqa xmm1, xmm0 ; grrr!
Samo to powoduje, że kwestionuję stan psychiczny optymalizatora. Oczywiście, dodatkowe MOVDQA nie zakłóca danych, ale w wartości nominalnej, wydaje się rażąco niedbałe ze strony gcc.
Wcześniej kodu montażowego (nie pokazano), xmm0 i XMM2 jest ustalony na pewną wartość znaczącą dla wektoryzacji, więc oczywiście na początku pętli, kod musi przejść pierwszy MOVDQA. Ale dlaczego nie zmienia się po prostu, jak pokazano poniżej.
.L3
movdqa xmm1, xmm0 ; initialize xmm1 PRIOR to loop
.L6
movdqa xmm0, xmm1 ; loop starts here
Albo jeszcze lepiej, po prostu zainicjować XMM1 zamiast xmm0 i zrzucić MOVDQA XMM1, xmm0 krok ogóle!
Jestem gotów uwierzyć, że procesor jest wystarczająco inteligentny, aby pominąć zbędny krok lub coś podobnego, ale jak mogę zaufać gcc, aby w pełni zoptymalizować złożony kod, jeśli może nawet uzyskać ten prosty kod w prawo? A może ktoś może dostarczyć solidne wyjaśnienie, które dałoby mi wiarę, że jest to dobre?
@Down wyborców: proszę skomentuj dlaczego. – Stefan
Czy kompilowano z włączonymi optymalizacjami. Na niektórych poziomach optymalizacji eliminowana jest operacja nadmiarowego przenoszenia. –
Czy jesteś pewien, że twój kod jest szybszy od kompilatorów? Czy próbowałeś je odmierzać? – Degustaf