OpenCL – Data Parallel
     Language
 Parallel Computing auf CPU,
         GPU und Cell
    Ein FG Workshop von Christopher Scherb
Inhalt
•
    Was bedeutet Data – Parallel?
•
    Was ist ein Streamprozessor?
•
    Was ist OpenCL?
•
    Wie programmiert man OpenCL?
•
    Grenzen?
•
    Beispiele
Was bedeutet Data –
               Parallel?
•   Verarbeitung von Daten zur gleichen Zeit
•   Multicore CPU
•   SSE bzw. SIMD
•   Streamprozessor
•   GPU


     for (int i = 0; i <         #pragma omp parallel
     datasize; i++)              num_threads(datasize)
     {                           {
           data[i] = data[i] +       int i = omp_get_thread_num();
     1;                              data[i] = data[i] + 1;
     }                           }
Was ist ein
           Streamprozessor?
•
    CO – Prozessor
•
    Viele kleine „Streamprozessoren“
•
    Zur Verarbeitung von Datenströmen
•
    Hohe Parallelität
•
    GPU?
Was ist OpenCL?
•
    Data/Task Parallel Language
•
    Für CPUs, GPUs, Cell und DSPs
•
    Von Apple entwickelt
•
    Verwaltet von der Khronos – Group
•
    Programmiert wird in OpenCL C
Plattform Model
Plattform Model
•
    Host = System – Prozessor (CPU)
•
    Compute Device = CPU / GPU / Cell
•
    Compute Unit = SIMD Einheit
•
    Prozessing Element = ALU des
    Compute Devices
Speichermodel
Speichermodel
•   Host Memory = System Memory
•   Global Memory = für alle Compute Units
•   Local Memory = für eine SIMD Einheit
•   Private Memory = für eine ALU


•   Nur Local Memory kann synchronisiert
    werden!
Ausführmodel
•   Verarbeite jedes Element „einzeln“
    –   Verteile auf verschiedene Compute Units
    –   SIMD wird nicht genutzt
•   Fasse bestimmte Anzahl der Elemente zu
    Blöcken zusammen
    –   SIMD
    –   Verteile mehrere Blöcke über die Compute Units
    –   Synchronisierung
Ausführmodel
Wie programmiert man
             OpenCL?
•   Host (System) initialisiert das OpenCL Device
•   Host erstellt und compiliert ein OpenCL
    Programm
•   Host erstellt Kernel (= ausführbarer Code)
•   Host schreibt Daten auf das Device
•   Device verarbeitet Daten
•   Host liest Daten zurück
Initialiesiere OpenCL Device
•   Wähle eine Platform (= SDK)
•   Lese Context Einstellungen aus Platform
•   Erstelle Context


•   Initialisiere Platform:
        cl_uint numPlatforms;
        cl_platform_id platform = NULL;
        clGetPlatformIDs(0, NULL,
      &numPlatforms);
Platform auswählen
if (0 < numPlatforms){
         cl_platform_id* platforms = new cl_platform_id[numPlatforms];
         clGetPlatformIDs(numPlatforms, platforms, NULL);
         for (unsigned i = 0; i < numPlatforms; ++i){
              char pbuf[100];

clGetPlatformInfo(platforms[i],CL_PLATFORM_VENDOR,sizeof(pbuf),pbuf,NUL
L);
            platform = platforms[i];
            if (!strcmp(pbuf, "Advanced Micro Devices, Inc."))break;
        }
        delete[] platforms;
    }

cl_context_properties cps[3] ={CL_CONTEXT_PLATFORM,
(cl_context_properties)platform,0};
cl_context_properties* cprops =(platform==NULL) ? NULL : cps;
Context erstellen

cl_context hContext;

hContext=clCreateContextFromType(cprops,CL_DEVICE_TYPE_GPU,
0,0,     &error);
     errcheck(error,"clCreateContextFromType");

Errorcheck Funktion (Sollte nach jeden Verweis auf
error aufgerufen werden):
void errcheck(int error, std::string error_code){
    if(error != CL_SUCCESS){
         std::cout<<"Error:
"<<error_code<<"("<<error<<")"<<std::endl;
         exit(-1);
    }
}
Lese Programm ein
•
      Lese Programm von Datei oder String
•
      Compiliere Programm

    std::ifstream file(<filename>);
    std::string prog(std::istreambuf_iterator<char>(file),
    (std::istreambuf_iterator<char>()));
    const char *sProgramSource=prog.c_str();

    hProgram = clCreateProgramWithSource(hContext,1,(
        const char **)&sProgramSource,0,&error);
    error = clBuildProgram(hProgram,0,0,0,0,0);
Erstelle Kernel
•
    Erstelle ausführbares Programm aus
    den compilierten Daten


    cl_kernel hKernel;
    hKernel=clCreateKernel(hProgram, <KernelName>,0);
Initialisiere Daten
•   Daten müssen gebuffert werden
•   Minimiere Transfer zwischen Host und Device!
•   Setze Kernel Argumente


cl_int *a=new cl_int[32];
cl_mem CL1;
CL1=clCreateBuffer(hContext,CL_MEM_READ_WRITE|
CL_MEM_COPY_HOST_PTR,
     sizeof(cl_int)*32,a, &error);
error = clSetKernelArg(hKernel,0,sizeof(cl_mem),(void *)&CL1);
Erstelle CommandQueue
•   Umgebung zum Ausführen von Kerneln
•   Wichtig für Synchronisierung zwischen verschiedenen Kerneln
•   Nutze gemeinsame Daten in verschiedenen Kerneln




hCmdQueue=clCreateCommandQueue(hContext,aDevices[0],0,0);
Führe die Queue aus
•   In Order oder Qut of Order
error = clEnqueueNDRangeKernel(hCmdQueue,
hKernel,1,0,<globalworksize>, <localworksize>,0,0,0,0);

• Lese Daten zurück
error=clEnqueueReadBuffer(hCmdQueue,CL1,CL_TRUE,0,sizeof(cl_int
)*32,a,0,0,0);


    • Free Memory
delete [] a;
clReleaseMemObject(CL1);
Wie sieht ein Programm
                   aus?
•    Vectoraddition
•    OpenCL C


    __kernel void vecadd(__global int* a, __global int* b, __global int*
    c)
    {
        int i = get_global_id(0);
        c[i] = a[i] + b[i];
    }
Grenzen
•
    Mergesort
•
    Letzter Mergevorgang nicht parallel
•
    Auf Streamprozessor langsam
•
    → Flexibel das Device wechseln
Demos
•
    Vectoraddition
•
    Mergesort
•
    Mandelbrot
Optimierungen
●   Maxiemiere Local Worksize
●   Code Vektorisierung (floatn)
●   Übertrage Vektoren statt Skalaren
●   Nutze Rechenleistung durch MIMD
●   OpenGL(D3D)/OpenCL Interaktion
    (Speicherreduzierung)

OpenCL Grundlagen

  • 1.
    OpenCL – DataParallel Language Parallel Computing auf CPU, GPU und Cell Ein FG Workshop von Christopher Scherb
  • 2.
    Inhalt • Was bedeutet Data – Parallel? • Was ist ein Streamprozessor? • Was ist OpenCL? • Wie programmiert man OpenCL? • Grenzen? • Beispiele
  • 3.
    Was bedeutet Data– Parallel? • Verarbeitung von Daten zur gleichen Zeit • Multicore CPU • SSE bzw. SIMD • Streamprozessor • GPU for (int i = 0; i < #pragma omp parallel datasize; i++) num_threads(datasize) { { data[i] = data[i] + int i = omp_get_thread_num(); 1; data[i] = data[i] + 1; } }
  • 4.
    Was ist ein Streamprozessor? • CO – Prozessor • Viele kleine „Streamprozessoren“ • Zur Verarbeitung von Datenströmen • Hohe Parallelität • GPU?
  • 5.
    Was ist OpenCL? • Data/Task Parallel Language • Für CPUs, GPUs, Cell und DSPs • Von Apple entwickelt • Verwaltet von der Khronos – Group • Programmiert wird in OpenCL C
  • 6.
  • 7.
    Plattform Model • Host = System – Prozessor (CPU) • Compute Device = CPU / GPU / Cell • Compute Unit = SIMD Einheit • Prozessing Element = ALU des Compute Devices
  • 8.
  • 9.
    Speichermodel • Host Memory = System Memory • Global Memory = für alle Compute Units • Local Memory = für eine SIMD Einheit • Private Memory = für eine ALU • Nur Local Memory kann synchronisiert werden!
  • 10.
    Ausführmodel • Verarbeite jedes Element „einzeln“ – Verteile auf verschiedene Compute Units – SIMD wird nicht genutzt • Fasse bestimmte Anzahl der Elemente zu Blöcken zusammen – SIMD – Verteile mehrere Blöcke über die Compute Units – Synchronisierung
  • 11.
  • 12.
    Wie programmiert man OpenCL? • Host (System) initialisiert das OpenCL Device • Host erstellt und compiliert ein OpenCL Programm • Host erstellt Kernel (= ausführbarer Code) • Host schreibt Daten auf das Device • Device verarbeitet Daten • Host liest Daten zurück
  • 13.
    Initialiesiere OpenCL Device • Wähle eine Platform (= SDK) • Lese Context Einstellungen aus Platform • Erstelle Context • Initialisiere Platform: cl_uint numPlatforms; cl_platform_id platform = NULL; clGetPlatformIDs(0, NULL, &numPlatforms);
  • 14.
    Platform auswählen if (0< numPlatforms){ cl_platform_id* platforms = new cl_platform_id[numPlatforms]; clGetPlatformIDs(numPlatforms, platforms, NULL); for (unsigned i = 0; i < numPlatforms; ++i){ char pbuf[100]; clGetPlatformInfo(platforms[i],CL_PLATFORM_VENDOR,sizeof(pbuf),pbuf,NUL L); platform = platforms[i]; if (!strcmp(pbuf, "Advanced Micro Devices, Inc."))break; } delete[] platforms; } cl_context_properties cps[3] ={CL_CONTEXT_PLATFORM, (cl_context_properties)platform,0}; cl_context_properties* cprops =(platform==NULL) ? NULL : cps;
  • 15.
    Context erstellen cl_context hContext; hContext=clCreateContextFromType(cprops,CL_DEVICE_TYPE_GPU, 0,0, &error); errcheck(error,"clCreateContextFromType"); Errorcheck Funktion (Sollte nach jeden Verweis auf error aufgerufen werden): void errcheck(int error, std::string error_code){ if(error != CL_SUCCESS){ std::cout<<"Error: "<<error_code<<"("<<error<<")"<<std::endl; exit(-1); } }
  • 16.
    Lese Programm ein • Lese Programm von Datei oder String • Compiliere Programm std::ifstream file(<filename>); std::string prog(std::istreambuf_iterator<char>(file), (std::istreambuf_iterator<char>())); const char *sProgramSource=prog.c_str(); hProgram = clCreateProgramWithSource(hContext,1,( const char **)&sProgramSource,0,&error); error = clBuildProgram(hProgram,0,0,0,0,0);
  • 17.
    Erstelle Kernel • Erstelle ausführbares Programm aus den compilierten Daten cl_kernel hKernel; hKernel=clCreateKernel(hProgram, <KernelName>,0);
  • 18.
    Initialisiere Daten • Daten müssen gebuffert werden • Minimiere Transfer zwischen Host und Device! • Setze Kernel Argumente cl_int *a=new cl_int[32]; cl_mem CL1; CL1=clCreateBuffer(hContext,CL_MEM_READ_WRITE| CL_MEM_COPY_HOST_PTR, sizeof(cl_int)*32,a, &error); error = clSetKernelArg(hKernel,0,sizeof(cl_mem),(void *)&CL1);
  • 19.
    Erstelle CommandQueue • Umgebung zum Ausführen von Kerneln • Wichtig für Synchronisierung zwischen verschiedenen Kerneln • Nutze gemeinsame Daten in verschiedenen Kerneln hCmdQueue=clCreateCommandQueue(hContext,aDevices[0],0,0);
  • 20.
    Führe die Queueaus • In Order oder Qut of Order error = clEnqueueNDRangeKernel(hCmdQueue, hKernel,1,0,<globalworksize>, <localworksize>,0,0,0,0); • Lese Daten zurück error=clEnqueueReadBuffer(hCmdQueue,CL1,CL_TRUE,0,sizeof(cl_int )*32,a,0,0,0); • Free Memory delete [] a; clReleaseMemObject(CL1);
  • 21.
    Wie sieht einProgramm aus? • Vectoraddition • OpenCL C __kernel void vecadd(__global int* a, __global int* b, __global int* c) { int i = get_global_id(0); c[i] = a[i] + b[i]; }
  • 22.
    Grenzen • Mergesort • Letzter Mergevorgang nicht parallel • Auf Streamprozessor langsam • → Flexibel das Device wechseln
  • 23.
    Demos • Vectoraddition • Mergesort • Mandelbrot
  • 24.
    Optimierungen ● Maxiemiere Local Worksize ● Code Vektorisierung (floatn) ● Übertrage Vektoren statt Skalaren ● Nutze Rechenleistung durch MIMD ● OpenGL(D3D)/OpenCL Interaktion (Speicherreduzierung)