Skocz do: nawigacji, wyszukiwania

MICLAB:Automatyczna wektoryzacja


Środowisko i modele programowania

dr inż. Łukasz Szustak, Politechnika Częstochowska, IITiS
mgr inż. Kamil Halbiniak, Politechnika Częstochowska, IITiS



Automatyczna wektoryzacja obliczeń


Automatyczna wektoryzacja jest najprostszym w użyciu sposobem wektoryzacji obliczeń. Nie wymaga ona ręcznej implementacji instrukcji wektorowych przez programistę. Umożliwia ona zamianę instrukcji skalarnych na wektorowe w trakcie kompilacji kodu źródłowego. Automatyczna wektoryzacja niewątpliwie przyspiesza proces tworzenia oprogramowania. Jednakże, ogranicza ona kontrolę nad sposobem wykonywaniem programu, ponieważ to kompilator decyduje jakie instrukcje wektorowe zostaną wykorzystane oraz w jakiej kolejności zostaną wykonane.


Automatyczna wektoryzacja obliczeń jest domyślną opcją w kompilatorach firmy Intel oraz kompilatorach gcc (od wersji 4.6.2) [28]. Następuje ona w momencie kompilacji kodu źródłowego z ustawioną flagą optymalizacji -O2. Dodatkowo programista może ją wyłączyć wykorzystując flagę -no-vec -no simd w przypadku kompilatorów firmy Intel lub -fno-tree-vectorize w przypadku kompilatorów gcc.


Proces automatycznej wektoryzacji często wymaga stosowania odpowiednich podpowiedzi dla kompilatora w celu efektywniejszego wykorzystania jednostek wektorowych. Jednakże, mechanizm ten działa poprawnie jedynie w przypadku kodu źródłowego, w którym nie występują zależności pomiędzy danymi. W przypadku kiedy, kompilator nie jest w stanie jednoznacznie określić zależności automatyczna wektoryzacja zakończy się niepowodzeniem. Należy wówczas wykorzystać dyrektywę #pragma ivdep, która instruuje kompilator, aby pominął zakładane zależności pomiędzy danymi. Przykład wektoryzacji kodu źródłowego, w którym występują zależności między danymi przedstawiono na Listingu 1.


#include <stdio.h>

int main()
{
   #pragma ivdep
   for(int i=1; i<100; ++i)
   {
      a[i] = a[i-1] * b[i];
   }
   return 0;
}
Listing 1. Autmatyczna wektoryzacja w przypadku kodu źródłowego, w którym występują zależności pomiędzy danymi


Efektywne wykorzystanie możliwości oferowanych przez jednostki wektorowe wymaga także dopasowania adresów w pamięci do odpowiedniej granicy. W przypadku produktów bazujących na architekturze Intel MIC granica ta wynosi 64-bajty. Rozwiązanie to zapewnia efektywniejsze wykonanie operacji zapisu oraz odczytu danych w pamięci, co na ogół skutkuje blisko 20% wzrostem wydajności obliczeń. Uzyskanie odpowiedniego dopasowanie adresów w przypadku tablic dynamicznych możliwe jest poprzez ich alokację oraz dealokację przy pomocy dedykowanych funkcji.


W przypadku języka C/C++ są nimi:

  • _mm_malloc(), która zwraca adres do obszaru w pamięci dopasowanego do wymaganej
granicy;
  • _mm_free(), która zwalnia przydzielony obszar pamięci.


W przypadku tablic statycznych w miejscu ich deklaracji, należy użyć następujących atrybutów:

  • w systemach Windows: __declspec(align( X ));
  • w systemach Linux: __attribute__((aligned( X )).


Przedstawione powyżej atrybuty są atrybutami języków C/C++. W miejsce X należy wprowadzić wartość określającą żądaną granicę dopasowania. Przykład alokacji danych wyrównanych w pamięci ilustruje Listing 2.


/*...*/
double* A = _mm_malloc(N * sizeof(double), 64);
float B[1000] __attribute__((aligned(32)));
/*...*/
_mm_free(A);
/*...*/
Listing 2. Alokacja danych z dopasowaniem


Wyrównanie adresów pozwala na wykonywanie obliczeń przy użyciu instrukcji operujących na dopasowanych danych. Jednakże wymaga to wprowadzenia w kodzie źródłowym dedykowanej dyrektywy #pragma vector aligned informującej kompilator, że obliczenia wykonywane będą na danych odpowiednio wyrównanych w pamięci. Bez tego kroku, kompilator nie może założyć, że dane są odpowiednio dopasowane. Przykład kodu, w którym zawarto pragmę informującą kompilator, że obliczenia wykonywane są na danych wyrównanych w pamięci do odpowiedniej granicy zaprezentowano na Listingu 3.


#include <stdio.h>

int main()
{
   /* alokacja danych z dopasowaniem */
   #pragma ivdep
   #pragma vector aligned
   for(int i=0; i<100; ++i)
   {
      a[i] = b[i] * c[i];
   }
   return 0;
}
Listing 3. Wskazówki kompilatora dotyczące dopasowania adresów


Istnieje również szereg innych dyrektyw wykorzystywanych do automatycznej wektoryzacji. Pozwalają one między innymi na zapis danych z pominięciem hierarchii pamięci czy wyłączenie wektoryzacji obliczeń przez kompilator. Wykorzystanie ich w kodzie źródłowym wymaga jedynie umieszczenia pożądanej dyrektywy przed pętlą, podobnie jak miało to miejsce w przykładach przedstawionych na Listingach 1 i 3. Pełny zestaw dyrektyw kompilatora wykorzystywanych do automatycznej wektoryzacji przedstawiono w Tabeli 1.


Tabela 1. Dyrektywy kompilatora wykorzystywane do automatycznej wektoryzacji
Dyrektywa kompilatora Opis
#pragma simd włączenie wektoryzacji
#pragma novector wyłączenie wektoryzacji
#pragma ivdep pominięcie zależności pomiędzy danymi
#pragma vector always ignorowanie heurystyk efektywności przy wektoryzacji obliczeń
#pragma vector nontemporal zapis danych z pominięciem hierarchii pamięci
#pragma vector [un]aligned potwierdzenie dopasowania bądź niedopasowania adresów
#pragma distribute point dyrektywa sugerująca podział dużej pętli na mniejsze
#pragma loop count (<int>) dyrektywa informująca o minimalnej maksymalnej oraz średniej liczbie iteracji pęli for



< Wektoryzacja obliczeń - Wprowadzenie

Manualna wektoryzacja obliczeń >