Saltar para o conteúdo

OpenMP

Origem: Wikipédia, a enciclopédia livre.

O OpenMP (do inglês Open Multi-Processing, ou Multi-processamento aberto) é uma interface de programação de aplicativo(API) para a programação multi-processo de memória compartilhada em múltiplas plataformas. Permite acrescentar simultaneidade aos programas escritos em C, C++ e Fortran sobre a base do modelo de execução fork-join. Está disponível em muitas arquiteturas, incluindo as plataforma Unix e Microsoft Windows. É constituída por um conjunto de diretivas de compilador, rotinas de biblioteca, e variáveis de ambiente que influenciam o comportamento do tempo de execução. Definido em conjunto por um grupo principal de fornecedores de hardware e de software, o OpenMP é um modelo de programação portável e escalável que proporciona aos programadores uma interface simples e flexível para o desenvolvimento de aplicações paralelas para as plataformas que vão dos computadores de escritório até os supercomputadores. Uma aplicação construída com um modelo de programação paralela híbrido pode ser executado em um cluster de computadores utilizando tanto os OpenMP e MPI, ou mais transparentemente através das extensões do OpenMP para os sistemas de memória distribuída.

OpenMP é uma implementação de multithreading, um método de paralelização no qual o "master thread"(uma série de instruções executadas consecutivamente) forks ("bifurca") um específico número de threads escravos e uma tarefa é dividida entre eles. Os thread são então executados simultaneamente, com ambiente de execução distribuindo as threads para diferentes processadores.

A parte de código que é criada para funcionar em paralelo é marcada de acordo com uma diretriz pré-processador que criará os thread para formar a secção antes de ser executada. Cada thread tem um "id" (endereço) anexado a ele, que pode ser obtido através de uma função (chamada omp_get_thread_num() em C / C + + e OMP_GET_THREAD_NUM() em Fortran). O thread "id" é um número inteiro e o master thread possui o id "0". Após a execução do código em paralelo, os "threads" retornam ao master thread, o qual continua progressivo até o fim do programa.

Por padrão, cada thread executa a seção do código paralelo independentemente. "Construções de Partilha" pode ser usada para dividir uma tarefa entre os threads de modo que cada thread execute a sua parte do código atribuído. Ambas as Task Parallelism (Paralelismo de tarefas) e Data parallelism (Paralelismo de dados) pode ser conseguida utilizando OpenMP desta forma.

O tempo de execução comum distribui threads aos processadores de acordo com a utilização, o desempenho da máquina e outros fatores. O números de threads pode ser fixado pelo ambiente de execução baseado em variaveis de ambiente ou um código usando funções. As funções do OpenMP estão incluidas no header file (biblioteca) rotulado "omp.h" em C/C++.

O Conselho de Análise e Arquitetura (Architecture Review Board, ARB) do OpenMP publicou suas primeiras especificações da API, para o OpenMP Fortran 1.0, em Outubro de 1997. Em Outubro do ano seguinte, foi liberado o C / C + + padrão. Em 2000 viu-se a versão 2.0 das especificações, com especificações do Fortran versão 2.0 do C / C + + sendo liberado em 2002. A versão 2.5 é um combinado das especificações C / C + + / Fortran que foi liberado em 2005. A versão 3.0, lançada em maio de 2008, é a atual versão das especificações da API. Incluídas nas novas funcionalidades do 3.0 estão o conceito de tasks (Tarefas) e a task construct (Construir Tarefa). Estas novas funcionalidades estão resumidas no Apêndice F do OpenMP 3.0 specifications.

Elementos centrais

[editar | editar código-fonte]

Os principais elementos do OpenMP são os construídos por "criação de fios de execução" (threads creations), "distribuição de carga do trabalho" (work sharing), "gestão do ambiente de dados" (data environment management), "sincronização de fios de execução" (thread synchronization), "tempo de execução das rotinas do usuário" (user level runtime routines) e "variáveis do ambiente" (environment variables). A diretriz em compilador C / C + + é chamado pragma (informação pragmática). A compilação de diretrizes específicas para OpenMP em C / C + + são escritas em códigos, conforme segue:

  1. pragma omp <rest de pragma>

Os pragmas específicos do OpenMP estão listados a seguir:

Criação de Threads

[editar | editar código-fonte]

omp paralelo. É utilizado para separar threads adicionais para a realização do trabalho incluso na construção em paralelo. O processo original será chamado master thread com ID 0. Exemplo: Exibição "Hello, world", utilizando múltiplas threads.

 #include <stdio.h>
 #include <omp.h>

 int main(int argc, char* argv[])
 {
   #pragma omp parallel
   printf("Hello, world.\n");
   return 0;
 }

Construção do compartilhamento

[editar | editar código-fonte]

Usado para especificar como atribuir um trabalho independente para um ou todos as threads.

  • omp for ou omp do: usado para fracionar os laços entre os caminhos de execução, também chamados "construtores de laço".
  • sections: atribuindo consecutivos, porém independentes, blocos de código para os diferentes threads.
  • single: especificando um bloco de código, que é então executado por apenas uma thread, com uma barreira implícita no fim
  • master: semelhante ao single, mas o bloco de código será executado apenas pela thread mestre e nenhuma barreira estará implícita no final.

Exemplo: inicializar o valor de uma grande variedade em paralelo, utilizando cada fio de execução para fazer parte do trabalho.

#include <stdio.h>
#include <omp.h>

int main(int argc, char **argv) {
    const int N = 100000;
    int i, a[N];

    #pragma omp parallel for
    for (i = 0; i < N; i++)
        a[i] = 2 * i;

    return 0;
}

Cláusulas do OpenMP

[editar | editar código-fonte]

Desde que foi compartilhada a programação modelo do OpenMP, a maior parte das variaveis no código do OpenMP são visíveis a todos threads por padrão. Porém algumas variaveis individuais são necessárias para evitar race conditions e existe a necessidade de passar valores entre a parte sequencial e a região paralela ( o código executado em paralelo ), para que os dados de gestão ambiental sejam introduzidos como as cláusulas atribuídas ao compartilhamento de dados colocando-lhes as diretrizes do OpenMP. Os diferentes tipos de cláusulas são:

Características da Partilha de Dados

[editar | editar código-fonte]
  • shared: os dados dentro de uma região paralela são compartilhados, o que significa visível e acessível por todas as threads em simultâneo. Por padrão, todas as variáveis da região de compartilhamento são partilhados, exceto o laço (loop).
  • private: os dados em uma região paralela são individuais para cada thread, com este recurso cada thread terá uma cópia local e será utilizada como uma variável temporária. Uma variável não é inicializada e o valor não é mantido para o uso fora da região paralela. Por padrão, os contadores no "construção do laço OpenMP" (OpenMP loop constructs) são individuais.
  • default: permite ao programador declarar a extensão do default data scoping (padrão de dados) que será compartilhado (ou nenhuma região) na região paralela, em C/C++, ou shared, firstprivate ou none para Fortran. A opção none força o programador a declarar cada variável na região paralela utilizando as cláusulas atribuídas ao compartilhamento de dados (data sharing attribute clauses).
  • firstprivate: como private exceto ao inicializar pelo valor original.
  • lasprivate: como private exceto que o valor original é atualizado depois da construção.
  • reduction: um caminho seguro de juntar todas as threads após a construção.

Sincronização

[editar | editar código-fonte]
  • critical section: o código incluso será executado por somente um thread por vez e não simultaneamente executado por múltiplos threads. É frequentemente usado para proteger os dados compartilhados das race conditions.
  • atomic: semelhante à critical section, mas informamos ao compilador para usar instruções especias de hardware para um melhor desempenho. Os compiladores podem optar por ignorar essa sugestão dos usuários e usar a critical section em vez da atomic.
  • ordered: o bloco estruturado é executado na ordem em que as iterações seriam executadas em um loop sequencial.
  • barrier: cada thread espera até que todos os outros threads de um grupo tenham alcançado este ponto. Uma construção de partilha tem uma barreira de sincronização implícita no final.
  • nowait: especifica quais threads podem completar sua instrução sem esperar todos os outros threads do grupo para concluir. Na ausência de tal cláusula acontece o mesmo da barrier.

Programação

[editar | editar código-fonte]
  • schedule (type chunk): É útil se a construção de partilha for um do-loop ou for-loop. A iteração, ou as iterações, na construção de partilha são atribuídas para as threads de acordo com o método programado definido nesta cláusula. Os três tipos de programações são:

1. static: Aqui, a todos os threads são atribuídas as iteração antes de executar o laço de repetição. As interações são divididas igualmente entre os threads por padrão. No entanto, especificando um inteiro para um parâmetro "chunk" fixará um número "chunk" de iterações sequenciadas a uma determinada lista de threads.

2. dynamic: Aqui, algumas das iterações são atribuídas a um número menor de threads. Após o término da iteração atribuída a um thread em particular, ele retorna para buscar uma das iterações restantes. O parâmetro "chunk" define o número de iterações sequenciais que são atribuídas a um thread por vez.

3. guided: Um grande "chunk" de iterações sequenciadas são atribuídos a cada thread dinamicamente (como acima). O tamanho do "chunk" diminui exponencialmente com cada atribuição sucessiva até um tamanho mínimo especificado no parâmetro chunk.

  • if: Isto fará com que as threads paralelizem a tarefa se a condição for satisfeita. Caso contrário, o bloco de código é executado em série.

Inicialização

[editar | editar código-fonte]
  • firstprivate: os dados são individuais para cada thread, mas inicializando o valor da variável usando o mesmo nome a partir da thread master.
  • lastprivate: os dados são individuais para cada thread. Se a iteração atual for a última iteração no loop paralelizado, o valor desses dados individuais serão copiados para uma variável global, usando o mesmo nome fora da região paralela. Uma variável pode ser ambas, firstprivate e lastprivate.
  • threadprivate: O dado é um dado global, mas é individual para cada região paralela durante a execução. A diferença entre threadprivate e private é que o escopo global é associado com o threadprivate e o valor é preservado além das regiões paralelas.

Cópia de dados

[editar | editar código-fonte]
  • copyin: semelhante ao firstprivate para variáveis privadas, variáveis threadprivate não são inicializadas, a não ser usando copyin para passar o valor das variáveis globais correspondentes. O copyout é necessária porque o valor de uma variável threadprivate é mantida durante toda a execução do programa inteiro.
  • copyprivate: usado como único apoio para a cópia dos valores dos dados a partir de objetos particulares em uma thread (a thread única) para os objetos correspondentes em outras threads do grupo.
  • reduction(operador | intrinsic: list) a variável tem uma copia local em cada thread, mas os valores das copias locais são resumidos (reduzidos) em uma variavel global compartilhada. Isso é muito útil se uma operação particular (especificada em "operador" para essa clausula particular) em um datatype que roda interativelmente então esse valor em uma interação particular depende do valor da interação anterior. Basicamente, os passos que mais dão importância ao incremento operacional são paralelizados, mas os threads reúnem-se e esperam antes de fazer o update dos datatype, depois incrementa o datatype em ordem para evitar perda de dados. Isso seria requerido paralelizando funções de integração numérica e equações diferenciadas, como um exemplo comum.
  • Flush: O valor desta variável é restaurado a partir do registro da memória para ser usado fora da parte paralela.
  • master: Executado apenas pela master thread (a thread que foi separada de todas as outras durante a execução do OpenMP). Sem barreira implícita; não é necessário que outros membros (threads) do grupo a alcancem.

Nível do usuário em rotinas de execução

[editar | editar código-fonte]

Usado para modificar / verificar o número de threads, detectar se o contexto de execução está em uma região paralela, a quantidade processadores no sistema atual, bloqueios set / unset, funções de calendário, etc.

Variáveis de ambiente

[editar | editar código-fonte]

É um método para alterar os recursos de execução de aplicativos OpenMP. Usado para controlar os loops de iterações, número de threads padrão, etc. Por exemplo OMP_NUM_THREADS é usado para especificar o número de tópicos para uma aplicação.

Exemplos de Programas

[editar | editar código-fonte]

Nesta seção, alguns programas de exemplo são fornecidos para ilustrar os conceitos explicados acima.

Este é um programa básico, que exerce o parallel, private e as barrier directives, bem como as funções omp_get_thread_num e omp_get_num_threads (não confundir).

JAVA

package omp.hello;
import jomp.runtime.OMP;

public class Hello_Normal {

	public static void main(String[] args) {

		int id;
		OMP.setNumThreads(15);

		//omp parallel private(id)
		{
			id = OMP.getThreadNum();
			System.out.println("Hello from thread: " + id);
		}
	}

}
 #include <omp.h>
 #include <stdio.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 0;
 }
#include <omp.h>
#include <iostream>
int main (int argc, char *argv[]) {
 int th_id, nthreads;
#pragma omp parallel private(th_id)
 {
  th_id = omp_get_thread_num();
  std::cout << "Hello World from thread" << th_id << "\n";
#pragma omp barrier
 if ( th_id == 0 ) {
   nthreads = omp_get_num_threads();
   std::cout << "There are " << nthreads << " threads\n";
  }
 }
 return 0;
}
      PROGRAM HELLO
      INTEGER ID, NTHRDS
      INTEGER OMP_GET_THREAD_NUM, OMP_GET_NUM_THREADS
C$OMP PARALLEL PRIVATE(ID)
      ID = OMP_GET_THREAD_NUM()
      PRINT *, 'HELLO WORLD FROM THREAD', ID
C$OMP BARRIER
      IF ( ID .EQ. 0 ) THEN
        NTHRDS = OMP_GET_NUM_THREADS()
        PRINT *, 'THERE ARE', NTHRDS, 'THREADS'
      END IF
C$OMP END PARALLEL
      END
 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

Cláusula de construção da partilha

[editar | editar código-fonte]

A aplicação de cláusulas OpenMp são ilustradas nos simples exemplos dessa seção. Esse pedaço de código abaixo mostra os elementos do vetor "b", demonstrando uma simples operação com os elementos do vetor "a". A parabolização é feita pelo diretivo OpenMP "#pragma". A sincronização de tarefas é dinâmica. Note como a interação dos pontos "j" e "k" tem que ser feito pilha de execução, portanto para fazer a tarefa total que lhe foi atribuída e atualizar a parte alocada da matriz "b", ao mesmo tempo como as outras threads.

 #define CHUNKSIZE 1 /*define o tamanho do pedaço com 1 iteração contigua*/
 /*forks saindo das threads*/
 #pragma omp parallel private(j,k)
 {
  /*nicia a construção de partilha*/
  #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;
 }

O próximo pedaço de código é um uso comum da reduction para calcular somas reduzidas. Aqui, nós adicionamos todos os elementos de um vetor "a" com um "i" contador, usando um for-loop que nós paralalizamos usando direvtives OpenMP e reduction. O cronograma permaneceu estático.

 #define N 10000 /*tamanho de um*/
 void calculate(int); /*A função calcula os elementos de um*/
 int i;
 long w;
 long a[N];
 calculate(a);
 long sum = 0;
 /*forks saindo das threads e começando a construção de partilha*/
 #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);

Um equivalente, porém menos elegante, a execução do código acima é para criar uma soma variável local para cada thread (loc_sum "), e fazer uma atualização protegido da variável global" soma "no final do processo, através da directiva" crítical ". Note que essa proteção é fundamental, como foi explicado em outro lugar.

 ...
 long sum = 0, loc_sum = 0;
 /*forks saindo das threads e começando a construção de partilha*/
 #pragma omp parallel for private(w,loc_sum) 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);

Implementações

[editar | editar código-fonte]

OpenMP foi implementada em vários compiladores comerciais. Por exemplo, Visual C++ o suporta (nas edicoes do "Professional and Team System"[1]), assim como "Intel Parellel Studio" para diversos processadores.[2] Compiladores e ferramentas Sun Studio suportam as ultimas especificacoes com ressalvas na produtividade para as plataformas Solaris SO e Linux.Os compiladores Fortran, C e C + + dO Portland Group também suportam OpenMP 2,5. O GCC também apoiou OpenMP desde a versão 4.2.

Alguns compiladores logo foram implementados para OpenMP 3.0, incluindo:

  • GCC 4.3.1
  • Compilador Nanos
  • Compiladores Intel Fortran e C / C + + versões de 11,0 a 11,1 e Intel Parallel Studio.
  • Compilador IBM XL C / C++.[3]

Sun Studio 12 update 1 tem plena aplicação do OpenMP 3.0.[4]

Prós e Contras

[editar | editar código-fonte]

Prós

  • Simples: não precisa lidar com a passagem de mensagens como MPI faz.
  • Data layout e decomposion são feitas automaticamente por meio de directivas.
  • Incremental parallelism: pode trabalhar em uma parte do programa de uma só vez,

nenhuma mudança dramática para o código é necessário.

  • Unified code tanto para aplicações seriais quanto para paralelas: OpenMP contructs

são tratadas como comentários quando compiladores seqüenciais são utilizados.

  • Original (de série) Em geral, o código não precisa de declarações para ser modificado

quando paralelizado com OpenMP.

Contras

  • Actualmente, só é executada de forma eficiente em multiprocessadores com memória compartilhada (contudo veja o Cluster Intel OpenMP).
  • Requer um compilador que suporta OpenMP.
  • A escalabilidade é limitada pela arquitetura de memória.
  • Manipulação segura do erro está em falta.
  • carece de mecanismo refinado para controlar o mapeamento thread-processor.

Expectativas de desempenho

[editar | editar código-fonte]

Pode-se esperar obter N vezes menos no tempo de execução do "wall clock" (ou N vezes speed up) quando executado um programa paralelizado utilizando OpenMP em uma plataforma de processador N. No entanto, isso raramente é o caso, devido às seguintes razões:

Uma grande parte do programa não pode ser paralelizada por OpenMP, o que significa que o limite teórico máximo de aceleração é de acordo com a lei de Amdahl.

N processadores em um SMP pode ter N vezes o poder de computação, mas a bandwidth de memória normalmente não escala até N vezes. Muitas vezes, o caminho de memória original é partilhada por vários processadores e a degradação do desempenho pode ser observada quando concorrem para a largura de banda de memória partilhada. Muitos outros problemas comuns que afetam o aumento de velocidade final em computação paralela também se aplicam a OpenMP, como balanceamento de carga e sobrecarga de sincronização.

Afinidade thread

[editar | editar código-fonte]

Alguns vendedores recomendam a configuração processor afinity em threads OpenMP para associá-los com núcleos de processador específico.[5][6][7] Isso minimiza a migração thread e o custo da comunicação entre o switch e os núcleos. Também melhora a localização dos dados e reduz o tráfego de coerência-cache entre os núcleos (ou processadores).

Existem alguns pontos de referência OpenMP de dominio público que o usuário pode experimentar:

Aprendizado por recursos on-line

[editar | editar código-fonte]
  • Quinn Michael J, Parallel Programming in C with MPI and OpenMP McGraw-Hill Inc. 2004. ISBN 0-07-058201-7
  • R. Chandra, R. Menon, L. Dagum, D. Kohr, D. Maydan, J. McDonald, Parallel Programming in OpenMP. Morgan Kaufmann, 2000. ISBN 1558606718
  • R. Eigenmann (Editor), M. Voss (Editor), OpenMP Shared Memory Parallel Programming: International Workshop on OpenMP Applications and Tools, WOMPAT 2001, West Lafayette, IN, USA, July 30-31, 2001. (Lecture Notes in Computer Science). Springer 2001. ISBN 354042346X
  • B.Chapman, G. Jost, R. vanderPas, D.J. Kuck, Using OpenMP: Portable Shared Memory Parallel Programming. The MIT Press (October 31, 2007). ISBN 0262533022
  • Parallel Processing via MPI & OpenMP, M. Firuziaan, O. Nommensen. Linux Enterprise, 10/2002

Referências

  1. MSDN Article
  2. David Worthington, "Intel addresses development life cycle with Parallel Studio" Arquivado em 15 de fevereiro de 2012, no Wayback Machine., SDTimes, 26 May 2009 (accessed 28 May 2009)
  3. "XL C/C++ for Linux Features", (accessed 9 June 2009)
  4. http://developers.sun.com/sunstudio/features/index.jsp
  5. «Multi-Core Software». Intel. 15 de novembro de 2007 
  6. «OMPM2001 Result». SPEC. 28 de janeiro de 2008 
  7. «OMPM2001 Result». SPEC. 1 de abril de 2003 

Ligações externas

[editar | editar código-fonte]