OpenMP

Giriş

OpenMP çoklu iş parçacığı gerçekleştirimidir. Çoklu iş parçacığı ana iş parçacığının(sırasıyla yürütülen komutların bir dizisi) belirli bir sayıda yardımcı iş parçacıklarını durdurması ve bir görev onlar arasında paylaştırması olan paralelleştirme metodudur. İş parçacıkları birbiri ardında paralel şekilde çalışırlar. Farklı işlemcilerin iş parçacıkları farklı çalışma zamanı ortamlarını kendilerine tahsis ederler.
Paralel çalışacak olan kodun bir bölümü sırasıyla işaretlenir. Bu işaretleme kod bölümünün yürütülmesinden önce iş parçacıklarının o kod bölümüne girmelerine sebep olacak ön işlemci direktifleridir. Her bir iş parçacığı onlara bağlı bir ID'ye sahiptir. omp_get_thread_num()fonksiyonu ile bu ID  elde edilir.İş parçacığı ID'si bir tam sayıdır ve ana iş parçacığının ID'si sıfırdır. Paralelleştirilmiş kodun yürütülmesinden sonra iş parçacıkları ana iş parçacığına tekrar geri katılırlar. Programın sonuna kadar bu böyle devam eder.
Varsayılan olarak her bir iş parçacığı kodun paralelleştirilmiş bölümünü birbirinden bağımsız şekilde yürütür. İş paylaşımı yapıları iş parçacıkları arasında bir görevi paylaştırmak için kullanılabilir, böylece her bir iş parçacığı kodun kendisine ayrılan bölümünde çalışır. Hem görev paralelleştirme hem de veri paralelleştirme OpenMP kullanarak bu yöntemle yapılır.
Çalışma zamanı ortamı kullanıma bağlı olarak işlemcilere iş parçacığı tahsis eder, makine yükleme ve diğer faktörler gibi. İş parçacıklarının sayısı ortam değişkenleri veya kod içerisinde kullanılan fonksiyonlara bağlı olarak çalışma zamanı ortamı tarafından atanır. OpenMP fonksiyonları omp.h etiketli C/C++ header dosyaları ile programa dahil edilir.

Tarihçe

OpenMP ARB ilk arayüz programını Fortran 1.0 için Ekim 1997 OpenMP ile yayınladı. Bir sonraki yıl C/C++ standartını piyasaya sürdüler. Fortran ve C/C++ 2.0 versiyonları ile birlikte 2002 yılında piyasaya sürüldü. 2.5 versiyonu C/C++ Fortranın bir kombinasyonu olarak 2005 yılında piyasaya sürüldü.
Mayıs 2008'de 3.0 versiyonu piyasaya sürüldü. Yeni özellikler görevler ve görev yapıları kavramları versiyonu 3.0'a dahil edildi.
Versiyon 3.1 9 Temmuz 2011'de piyasaya sürüldü.
Versiyon 4.0'ın 2013 Haziran veya Temmuz aylarında sunulması bekleniyor. Aşağıdaki özellikleri geliştirmiş ve kendisine eklemiştir. Hızlandırıcılar içi destek, atomikler, hata yönetimi,işparçacığı ilişkisi, görev geliştirimi, kullanıcı tanımlı arttırma, SIMD desteği, Fortran 2003 desteği.

Temel elemanlar

OpenMP temel elemanları iş parçacığı oluşturma iş yükü dağıtımı(iş paylaşımı), veri ortam yönetimi, iş parçacığı senkronizasyonu, kullanıcı seviyeli çalışma zamanı rutinleri ve ortam değişkenleri için yapılardır.
C/C++' ta OpenMP #pragma etiketi kullanılır. OpenMP özel pragmalar aşağıda listelenmiştir.

İş Parçacığı Oluşturma

omp parallel pragma paralelleştirilmiş yapı içerisinde kapanmış işi gerçekleştirmek için ek iş parçacıklarını çapraz dağıtmak için kullanılır. Orijinal iş parçacığı ID=0 olan ana iş parçacığı olarak gösterilir.
Örnek (C program): Çoklu iş parçacığı kullanarak "Merhaba Dünya" yazdırmak.
#include <stdio.h>int main(void){
  #pragma omp parallel
    printf("Merhaba, dünya.\n");
  return 0;}
GCC kullanarak derlemek için -fopenmp bayrağı kullanılır: 
$gcc -fopenmp merhaba.c -o merhaba
' iş parçacıklı ve 2 çekirdekli bilgisayar üzerindeki çıktı.
Merhaba, dünya.
Merhaba, dünya.
Bununla birlikte çıktı yarış durumları sebebiyle 2 iş parçacığının standart çıktıyı paylaşmasından dolayı bozulmuş bir şekilde alınabilir.
Merhaba, düMerha, düünya
nya.

OpenMP Cümlecikleri

OpenMP paylaşımlı bellek programlama modeli olduğu için OpenMP kodu içerisindeki çoğu değişken varsayılan olarak tüm iş parçacıklarına görünür, ama bazen özel değişkenlerin yarış durumlarından kaçınması gereklidir ve seri bölge ile paralel bölge arasındaki değerlerin işlenebilmesi için veri ortam yönetimi veri paylaşım özellik cümlecikleri olarak tanıtılmıştır. Bu cümlecikler OpenMP direktiflerine eklenir

Kullanıcı seviyesi çalışma rutinleri

İş parçacıklarının sayısını değiştirmek ve kontrol etmek, eğer yürütüm paralel bölge içerisinde ise tespit etmek ve o an sistemde kaç tane işlemcinin çalıştığını belirlemek için kullanılır.

Ortam Değişkenleri

OpenMP uygulamalarının yürütme özelliklerini değiştirmek için kullanılan bir metottur. Kontrol döngü iterasyonlarını zamanlamak, iş parçacıklarının sayılarını varsayılan yapmak için kullanılır. Mesela OMP_NUM_THREADS bir uygulama için işparçacıklarının sayılarını belirlemede kullanılır.

Örnek programlar

Bu bölümde bazı örnek programlar üstte açıklanan kavramları göstermek için verilmiştir.

Merhaba Dünya

parallel, private ve barrier direktiflerini örnekleyen temel bir program ve  omp_get_thread_num ve omp_get_num_threads fonksiyonlarını kullanır.

C

Bu C programı -fopenmp bayrağıyla birlikte gcc 4.4 kullanılarak derlenmiştir.
#include <omp.h>#include <stdio.h>#include <stdlib.h>int main (int argc, char *argv[]) {
  int th_id, nthreads;
  #pragma omp parallel private(th_id)
  {
    th_id = omp_get_thread_num();
    printf("Hello World from thread %d\n", th_id);
    #pragma omp barrier
    if ( th_id == 0 ) {
      nthreads = omp_get_num_threads();
      printf("There are %d threads\n",nthreads);
    }
  }
  return EXIT_SUCCESS;}

C++

Bu C++ programı GCC: gcc -Wall -fopenmp test.cpp -lstdc++ kullanılarak derlenmiştir.
#include <iostream>using namespace std;#include <omp.h>int main(int argc, char *argv[]){
  int th_id, nthreads;
  #pragma omp parallel private(th_id) shared(nthreads)
  {
    th_id = omp_get_thread_num();
    #pragma omp critical
    {
      cout << "Hello World from thread " << th_id << '\n';
    }
    #pragma omp barrier

    #pragma omp master
    {
      nthreads = omp_get_num_threads();
      cout << "There are " << nthreads << " threads" << '\n';
    }
  }

  return 0;}

Fortran 77

Bu da Fortran 77 versiyonu.
      PROGRAM HELLO
      INTEGER ID, NTHRDS
      INTEGER OMP_GET_THREAD_NUM, OMP_GET_NUM_THREADSC$OMP PARALLEL PRIVATE(ID)
      ID = OMP_GET_THREAD_NUM()
      PRINT *, 'HELLO WORLD FROM THREAD', IDC$OMP BARRIER
      IF ( ID .EQ. 0 ) THEN        NTHRDS = OMP_GET_NUM_THREADS()
        PRINT *, 'THERE ARE', NTHRDS, 'THREADS'
      END IFC$OMP END PARALLEL
      END

Fortran 90 serbest biçim

Bu da Fortran 90 serbest biçim versiyonu.
 program hello90
 use omp_lib
 integer:: id, nthreads
   !$omp parallel private(id)
   id = omp_get_thread_num()
   write (*,*) 'Hello World from thread', id
   !$omp barrier
   if ( id == 0 ) then     nthreads = omp_get_num_threads()
     write (*,*) 'There are', nthreads, 'threads'
   end if
   !$omp end parallel
 end program

C/C++ İş Paylaşımı Yapılarındaki Cümlecikler

Bazı OpenMP cümleciklerinin uygulaması bu bölümde basit örneklerle gösterilmiştir. Aşağıdaki kod parçası a dizisinin elemanları üzerinde basit bir operasyon işleterek b dizisinin elemanlarını günceller. Paralelleştirme OpenMP direktifi #pragma omp tarafından yapılır. Görevlerin zamanlanması dynamictir. İterasyon sayaçları j ve k'nın özel olmak zorunda olduğunu ve ilkel iterasyon sayacı i'nin varsayılan olarak özel olduğunu görebilirsiniz. i boyunca çalışan görevler çoklu iş parçacıkları arasında paylaştırılmıştır ve her iş parçacığı j ve k'nın kendi versiyonlarını oluştururlar. Bu yüzden tüm görevin tahsisini yapmak ve b dizisinin tahsis edilmiş parçasını güncellemek diğer iş parçacıklarıyla aynı zamanda yapılır.
 #define CHUNKSIZE 1 /*defines the chunk size as 1 contiguous iteration*/
 /*forks off the threads*/
 #pragma omp parallel private(j,k) 
 {
  /*Starts the work sharing construct*/
  #pragma omp for schedule(dynamic, CHUNKSIZE)
  for(i = 2; i <= N-1; i++)
     for(j = 2; j <= i; j++)
        for(k = 1; k <= M; k++)
           b[i][j] +=   a[i-1][j]/k + a[i+1][j]/k;
 }
Diğer kod parçacığı indirgeme cümleciğinin indirgenmiş toplamlarını hesaplamak için yaygın kullanımını gösterir. Burada OpenMp direktifleri ve indirgeme cümlecikleri kullanarak paralelleştirdiğimiz for döngüsünü içindeki i ile birlikte tüm a dizisinin elemanlarını ekliyoruz. Zamanlama static sürdürülür.
 #define N 10000 /*size of a*/
 void calculate(long *); /*The function that calculates the elements of a*/
 int i;
 long w;
 long a[N];
 calculate(a);
 long sum = 0;
 /*forks off the threads and starts the work-sharing construct*/
 #pragma omp parallel for private(w) reduction(+:sum) schedule(static,1)
 for(i = 0; i < N; i++)
    {
      w = i*i;
      sum = sum + w*a[i];
    }
 printf("\n %li",sum);
Üstteki kodun gerçekleştirimin bir dengi her bir iş parçacığı için yerel sum değişkeni kullanmaktır("loc_sum") ve sürecin sonunda global sum değişkeninin güncellenmesini korumaktır. Aşağıda açıklandığı gibi bu koruma önemlidir
 ...
 long sum = 0, loc_sum;
 /*forks off the threads and starts the work-sharing construct*/
 #pragma omp parallel private(w,loc_sum)
 {
   loc_sum = 0;
   #pragma omp for schedule(static,1)
   for(i = 0; i < N; i++)
     {
       w = i*i;
       loc_sum = loc_sum + w*a[i];
     }
   #pragma omp critical
   sum = sum + loc_sum;
 }
 printf("\n %li",sum);