2012-06-08 14 views
14

Po raz pierwszy pracuję z wewnętrzną implementacją SSE. Próbuję przekonwertować prosty fragment kodu na szybszą wersję, używając Intel SSE intrinsic (upto SSE4.2). Wydaje mi się, że napotykam wiele błędów.Optymalizowanie kodu za pomocą Intel® Intel SSE do wektoryzacji

Skalar wersja kodu jest: (proste mnożenie macierzy)

 void mm(int n, double *A, double *B, double *C) 
    { 
     int i,j,k; 
     double tmp; 

     for(i = 0; i < n; i++) 
      for(j = 0; j < n; j++) { 
        tmp = 0.0; 
        for(k = 0; k < n; k++) 
          tmp += A[n*i+k] * 
            B[n*k+j]; 
        C[n*i+j] = tmp; 

       } 
      } 

To jest moja wersja: Mam włączone #include

 void mm_sse(int n, double *A, double *B, double *C) 
     { 
     int i,j,k; 
     double tmp; 
     __m128d a_i, b_i, c_i; 

     for(i = 0; i < n; i++) 
      for(j = 0; j < n; j++) { 
        tmp = 0.0; 
        for(k = 0; k < n; k+=4) 
          a_i = __mm_load_ps(&A[n*i+k]); 
          b_i = __mm_load_ps(&B[n*k+j]); 
          c_i = __mm_load_ps(&C[n*i+j]); 

          __m128d tmp1 = __mm_mul_ps(a_i,b_i); 
          __m128d tmp2 = __mm_hadd_ps(tmp1,tmp1); 
          __m128d tmp3 = __mm_add_ps(tmp2,tmp3); 
          __mm_store_ps(&C[n*i+j], tmp3); 

      } 
     } 

Gdzie ja mam nie tak z tym? Dostaję kilka błędów tak:

mm_vec.c (84): error: wartość typu "int" nie może być przypisany do jednostki typu "__m128d" a_i = __mm_load_ps (& A [n * i + k]);

ten sposób jestem kompilacji: ICC -O2 mm_vec.c -o vec

Czy ktoś mógłby mi pomóc konwertowania ten kod dokładnie. Dzięki!

UPDATE:

Według swoimi sugestiami, dokonaniu następujących zmian:

 void mm_sse(int n, float *A, float *B, float *C) 
     { 
     int i,j,k; 
     float tmp; 
     __m128 a_i, b_i, c_i; 

     for(i = 0; i < n; i++) 
      for(j = 0; j < n; j++) { 
        tmp = 0.0; 
        for(k = 0; k < n; k+=4) 
          a_i = _mm_load_ps(&A[n*i+k]); 
          b_i = _mm_load_ps(&B[n*k+j]); 
          c_i = _mm_load_ps(&C[n*i+j]); 

          __m128 tmp1 = _mm_mul_ps(a_i,b_i); 
          __m128 tmp2 = _mm_hadd_ps(tmp1,tmp1); 
          __m128 tmp3 = _mm_add_ps(tmp2,tmp3); 
          _mm_store_ps(&C[n*i+j], tmp3); 


      } 
     } 

Ale teraz wydaje się być coraz usterki segmentacji. Wiem o tym, być może dlatego, że nie mam dostępu do tablic indeksów poprawnie dla tablic A, B, C. Jestem na to nowy i nie wiem, jak to zrobić.

Proszę pomóż mi określić prawidłowe podejście do obsługi tego kodu.

Odpowiedz

9

Błąd widzisz to dlatego, że masz zbyt wiele podkreślenia w nazwach funkcji, np:

__mm_mul_ps 

powinno być:

_mm_mul_ps // Just one underscore up front 

więc kompilator C przy założeniu wrócą int ponieważ nie widziała deklaracji.

Po tym wszystkim pojawią się dalsze problemy - wydaje się, że mieszasz połączenia z podwójnymi i pojedynczymi wariantami tej samej instrukcji.

Na przykład masz:

__m128d a_i, b_i, c_i; 

ale zadzwonić:

__mm_load_ps(&A[n*i+k]); 

który zwraca nie __m128__m128d - chciał zadzwonić:

_mm_load_pd 

zamiast. Podobnie w przypadku innych instrukcji, jeśli chcesz, aby działały na parach podwójnych.


Jeśli widzisz niewyjaśnionych naruszenie ochrony pamięci w kodzie SSE byłbym skłonny się domyślić, że masz problemy z pamięcią wyrównania - wskazówki przekazywane do intrinsics SSE (głównie) muszą być 16 bajt wyrównany. Możesz check this with a simple assert w swoim kodzie lub sprawdzić go w debugerze (oczekujesz, że ostatnia cyfra wskaźnika będzie równa 0, jeśli jest odpowiednio wyrównana).

Jeśli nie jest wyrównany w prawo, należy się upewnić, że jest. Rzeczy nie przydzielonych z new/malloc() można to zrobić z rozszerzeniem kompilatora (np with gcc):

float a[16] __attribute__ ((aligned (16))); 

Pod swoją wersję gcc ma max wyrównanie na tyle duże, aby wspierać ten i kilka innych ostrzeżenia odnośnie wyrównania stosu . W przypadku dynamicznie przydzielanej przestrzeni dyskowej należy użyć rozszerzenia specyficznego dla platformy, np. posix_memalign przeznaczyć odpowiednie przechowywanie:

float *a=NULL; 
posix_memalign(&a, __alignof__(__m128), sizeof(float)*16); 

(myślę, że mogłyby być ładniejsze, przenośne sposoby robienia tego z C++ 11, ale nie jestem w 100% pewien, że na razie).

Istnieje kilka instrukcji, które pozwalają wykonywać niezalecane ładunki i magazyny, ale są one strasznie wolne w porównaniu do wyrównanych obciążeń i warte uniknięcia, jeśli w ogóle możliwe.

+0

Pracuję z icc nie gcc. Czy uważasz, że obsługa tego w następujący sposób: a_i = _mm_load_ps (& A [n * i + k]), czy to właściwe podejście? Przykłady, które widzę gdzie indziej (nawet w dokumentacji Intrinsic Intela) mają bardzo proste przykłady. tablice A, B, C zostały przydzielone za pomocą malloc. – PGOnTheGo

+1

@Hello_PG Ładunek nie jest bezpośrednio błędny. Nie musisz jednak ładować c_i. W przeważającej części ICC ma takie same rozszerzenia jak gcc - myślę, że tak jest w przypadku wyrównania, jestem bardziej zaznajomiony z GCC niż z ICC osobiście, więc zakwalifikowałem go i powiązałem z dokumentami, które umiałem znaleźć. malloc nie gwarantuje odpowiedniego dopasowania na wszystkich platformach, więc posix_memalign jest prawdopodobnie potrzebny. Czy twierdzenie, które zasugerowałem, zawiodło? – Flexo

+0

Kiedy próbuję przydzielić pamięć dla A w ten sposób: A = (float *) _ aligned_malloc (wymiar * wymiar * sizeof (float), 16); Dostaję błąd kompilacji: niezdefiniowane odwołanie do "aligned_malloc" z icc. tak właśnie kompiluję: icc -O2 mm_vec.c -o vec2 – PGOnTheGo

3

Musisz upewnić się, że twoje ładunki i sklepy mają zawsze dostęp do 16-bajtowych adresów wyrównanych. Alternatywnie, jeśli nie możesz tego zagwarantować z jakiegoś powodu, użyj _mm_loadu_ps/_mm_storeu_ps zamiast _mm_load_ps/_mm_store_ps - będzie to mniej wydajne, ale nie spowoduje awarii na nieprawidłowo ustawionych adresach.

Powiązane problemy