2015-02-24 13 views
7

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?

+0

@Down wyborców: proszę skomentuj dlaczego. – Stefan

+0

Czy kompilowano z włączonymi optymalizacjami. Na niektórych poziomach optymalizacji eliminowana jest operacja nadmiarowego przenoszenia. –

+1

Czy jesteś pewien, że twój kod jest szybszy od kompilatorów? Czy próbowałeś je odmierzać? – Degustaf

Odpowiedz

4

nie jestem w 100% pewien, ale wygląda na to pętla niszczy xmm0 poprzez przekształcenie go do float, tak, aby mieć wartość całkowitą w xmm1 a następnie skopiować do innego rejestru (w tym przypadku xmm0).

Podczas gdy kompilatory są znane z tego, że czasami wydają niepotrzebne instrukcje, tak naprawdę nie widzę, jak to jest w tym przypadku.

Jeśli chcesz xmm0 (lub xmm1) pozostawania całkowitą, to nie ma obsady float dla pierwszej wartości i. Być może to, co chciał zrobić, to:

for (i=0;i<100;i++) 
    a[i]= (float)(i*i); 

Ale z drugiej strony, gcc 4.9.2 nie wydaje się, aby to zrobić:

g++ -S -O3 floop.cpp 

.L2: 
    cvtdq2ps %xmm1, %xmm0 
    mulps %xmm0, %xmm0 
    addq $16, %rax 
    paddd %xmm2, %xmm1 
    movaps %xmm0, -16(%rax) 
    cmpq %rbp, %rax 
    jne .L2 

Nie ma dzyń (3.7.0 od około 3 tydzień temu)

clang++ -S -O3 floop.cpp 


    movdqa .LCPI0_0(%rip), %xmm0 # xmm0 = [0,1,2,3] 
    xorl %eax, %eax 
    .align 16, 0x90 
.LBB0_1:        # %vector.body 
             # =>This Inner Loop Header: Depth=1 
    movd %eax, %xmm1 
    pshufd $0, %xmm1, %xmm1  # xmm1 = xmm1[0,0,0,0] 
    paddd %xmm0, %xmm1 
    cvtdq2ps %xmm1, %xmm1 
    mulps %xmm1, %xmm1 
    movaps %xmm1, (%rsp,%rax,4) 
    addq $4, %rax 
    cmpq $100, %rax 
    jne .LBB0_1 

kod, który mam skompilowane:

extern int printf(const char *, ...); 

int main() 
{ 
    int i; 
    float a[100]; 

    for (i=0;i<100;i++) 
     a[i]= (float) i*i; 

    for (i=0; i < 100; i++) 
     printf("%f\n", a[i]); 
} 

(Dodałem printf, aby uniknąć kompilacji, aby pozbyć się CAŁEGO kodu)

+0

Ale tak właśnie się dzieje. Jeśli spojrzysz na zgromadzenie, zobaczysz, że xmm0 jest konwertowane na pływające, podniesione do kwadratu i zapisane. Pytanie brzmi, dlaczego kompilator zastępuje xmm1 po skoku pętli. – Marandil

+0

Ah, dobra uwaga. Jest to po prostu kolejny przypadek "pisania kompilatorów jest trudny".Jeśli masz ochotę na wyzwanie, powiedziałbym, że możesz spróbować znaleźć miejsce w gcc i zaproponować poprawkę. –

+1

Czy może po prostu uaktualnić do nowszego gcc? –

Powiązane problemy