Automatisk vektorisering

För några dagar sedan såg jag en video som presenterade en ny funktion i Visual Studio 2012. Funktionen var automatisk vektorisering.  Videon gjorde mig nyfiken på funktionen, hur optimeringen fungerar och om den finns i de verktyg som jag har tillgång till, dvs GCC.

Det korta svaret är att det gör det! Om du använder optimeringsnivå tre, kompileringsväljare -O3, så får du denna optimering automatiskt. Enligt videon så är det samma visa för Visual Studio 2012. Optimeringen sker automatisk. Så det är ju bra och historien kan sluta där om du vill. Men 0m du vill ha lite mer detaljer så läs på.

Hur fungerar den här optimeringen?

Iden är att använda de speciella register som teknologier som t.ex. MMX eller SSE introducerade. Dessa vektorregister kan innehålla flera skalära värden och utföra operationer med dem samtidigt, parallellt. Det är något som t.ex. kan ske när man går igenom alla element i en vektor.

int a[SIZE], b[SIZE], c[SIZE];
...
for (i=0; i<SIZE; i++)
{
   a[i] = b[i] + c[i];
}

Det som utförs i loopen kan vektoriseras genom att additionen för mer än ett index i taget kan utföras parallellt genom att använda vektorregistren. Så det gäller för kompilatorn att analysera koden för att se om optimeringen kan tillämpas.

Hur aktiverar man den?

Med GCC är som sagt denna optimering på från optimeringsnivå 3. Du kan också aktivera den med väljaren -ftree-vectorize. Notera dock att du också måste aktivera användningen av vektorregistren. På min x86-maskin behöver jag lägga till väljaren -msse2.

Det finns en annan användbart väljare och det är -ftree-vectorize-verbose. Den gör så att kompilatorn berättar om den hittar en loop som den kan optimera. Det är bra eftersom optimeringar är lite som en svart låda. Man måste antingen köra programmet och mäta prestanda eller så får man analysera assemblerkoden för att förstå om optimeringen har skett. Det är besvärligt. En tydligt meddelande är en stor hjälp så att man vet man är på rätt väg.

Kommer mitt program att bli snabbare?

Givetvis måste det här testas. Här kommer ett litet testprogram i C++11.

// Vectorization

#include <chrono>
#include <iostream>

using namespace std;

const int SIZE=2048;
int a[SIZE], b[SIZE], c[SIZE];

void foo ()
{
   for (int j=0; j<1000000; j++)
   {
      for (int i=0; i<SIZE; i++)
      {
         a[i] = 0;
         b[i] = 10;
         c[i] = 100;
      }

      for (int i=0; i<SIZE; i++)
      {
         a[i] = b[i] + c[i];
      }
   }
}

int main()
{
   auto start = chrono::steady_clock::now();

   foo();

   auto diff = chrono::steady_clock::now() - start;
   auto ms = chrono::duration_cast<chrono::milliseconds>(diff);
   cout << "It took " << ms.count() << endl;
}

För att få något mätbart så körs loopen en miljon gånger. Den optimerade versionen tog 1122 millisekunder och den ej optimerade tog 4424 millisekunder. Det är cirka fyra gånger snabbare. Det beror på att på den aktuella maskinen är ett vektorregister 128 bitar och en integer 32 bitar. Varje register kan alltså lagra fyra värden samtidigt och vi kan göra fyra operationer parallellt.

Sammanfattning

Vektorisering är en optimeringsteknik som kan användas med automatik av en smart kompilator. Det är dessutom lätt att förstå hur den fungerar. Program som kommer dra nytta av det är de som gör upprepade oberoende operationer över stora datamängder.

Intressant är att optimeringen använder resurser som finns i en CPU i en kärna. Den illustrerar dock hur exekvering kan snabbas upp genom att använda parallellism för oberoende operationer och för vidare till hur C++ AMP, Accelerated Massive Parallelism fungerar vilket är något vi får återkomma till.

 

Publicerad i C/C++

Kategorier

WP to LinkedIn Auto Publish Powered By : XYZScripts.com