Aprendendo a usar a estrutura OpenMP com GCC

A estrutura Open Multiprocessing (OpenMP) é uma especificação extremamente eficiente, que ajuda a aproveitar os benefícios de sistemas com mais de um processador com aplicativos C, C++ e Fortran. Este artigo explica como usar os recursos do OpenMP em código C++ e traz exemplos que podem ajudar o leitor a começar a usar o OpenMP.

Arpan Sen, Independent author

Arpan Sen trabalha como engenheiro chefe no desenvolvimento de software no segmento de mercado de automação de design eletrônico. Ele trabalhou em várias versões do UNIX, incluindo Solaris, SunOS, HP-UX e IRIX, além de Linux e Microsoft Windows® por vários anos. Possui um grande interesse por técnicas de otimização do desempenho de software, teoria de gráfico e computação paralela. Arpan possui pós-doutorado em sistemas de software. É possível entrar em contato com ele através do email arpansen@gmail.com



08/Out/2012

A estrutura OpenMP é uma maneira eficiente de fazer programação simultânea em C, C++ e Fortran. O GNU Compiler Collection (GCC) versão 4.2 tem suporte para o padrão OpenMP 2.5, enquanto o 4.4 tem suporte para o padrão OpenMP 3, mais recente. Outros compiladores, como o Microsoft® Visual Studio, também têm suporte para OpenMP. Neste artigo, você aprenderá a usar pragmas de compilador da estrutura OpenMP, a encontrar suporte para algumas das interfaces de programação de aplicativos (APIs) que ela fornece e a testá-la com alguns algoritmos paralelos. Este artigo usa GCC 4.2 como o compilador preferencial.

Introdução

Um ótimo recurso do OpenMP é que não é necessário nada além da instalação padrão do GCC. Programas com OpenMP devem ser compilados com a opção -fopenmp.

O seu primeiro programa de OpenMP

Vamos começar com um simples programa para exibir a frase Hello, World!, que inclui um pragma adicional. A Listagem 1 mostra o código.

Lista 1. Hello World com OpenMP
#include <iostream>
int main()
{
  #pragma omp parallel
  {
    std::cout << "Hello World!\n";
  }
}

Quando o código da Listagem 1 é compilado e executado com g++, uma única mensagem Hello, World! deve ser exibida no console. Agora, recompile o código com a opção -fopenmp. A Listagem 2 mostra a saída.

Lista 2. Compilando e executando o código com o comando -fopenmp
tintin$ g++ test1.cpp -fopenmp
tintin$ ./a.out 
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

O que aconteceu? A mágica de #pragma omp parallel funciona apenas quando a opção -fopenmp do compilador é especificada. Internamente, durante a compilação, o GCC gera o código para criar o máximo número de encadeamentos possível no tempo de execução, com base no hardware e na configuração do sistema operacional. A rotina de início de cada encadeamento é o código no bloco após o pragma. Esse comportamento chama-se paralelização implícita. Na sua essência, OpenMP consiste em um conjunto de pragmas eficientes que livram o desenvolvedor da obrigação de incluir muitos códigos repetidos. (Para comparação, confira como seria uma implementação do que você acaba de fazer em [pthreads], os encadeamentos da Interface de Sistema Operacional Portátil (POSIX).) Como eu estou usando um computador com um processador Intel® Core i7, com quatro núcleos físicos e dois núcleos lógicos por núcleo físico, a saída da Listagem 2 parece adequada (8 encadeamentos = 8 núcleos lógicos).

Agora vamos ver mais detalhes sobre pragmas paralelos.


Brincando com OpenMP paralelo

Para controlar o número de encadeamentos, basta usar o argumento num_threads do pragma. Aqui está novamente o código da Listagem 1 com o número de encadeamentos disponíveis especificados como 5 (como mostra a Listagem 3).

Lista 3. Controlando o número de encadeamentos com num_threads
#include <iostream>
int main()
{
  #pragma omp parallel num_threads(5) 
  {
    std::cout << "Hello World!\n";
  }
}

Em vez da abordagem num_threads, aqui está uma alternativa para alterar o número de encadeamentos executando o código. Essa também é a primeira API do OpenMP que você irá usar: omp_set_num_threads. Essa função é definida no arquivo de cabeçalho omp.h. Não é necessário vincular outras bibliotecas para que o código na Listagem 4 funcione — apenas -fopenmp.

Lista 4. Uso de omp_set_num_threads para ajuste fino da criação de encadeamentos
#include <omp.h>
#include <iostream>
int main()
{
  omp_set_num_threads(5); 
  #pragma omp parallel 
  {
    std::cout << "Hello World!\n";
  }
}

O OpenMP também usa variáveis de ambiente externas para controlar seu comportamento. É possível alterar o código na Listagem 2 para imprimir apenas Hello World! seis vezes, definindo a variável OMP_NUM_THREADS para 6. A Listagem 5 mostra a execução.

Lista 5. Usando variáveis de ambiente para ajustar o comportamento do OpenMP
tintin$ export OMP_NUM_THREADS=6
tintin$ ./a.out 
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!

Você já descobriu todas as três facetas do OpenMP: pragmas do compilador, APIs do tempo de execução e variáveis de ambiente. O que acontece se usarmos a variável de ambiente e a API do tempo de execução? A API tem precedência mais alta.

Um exemplo prático

O OpenMP usa técnicas de paralelização implícita, e é possível usar pragmas, funções explícitas e variáveis de ambiente para instruir o compilador. Vamos examinar um exemplo no qual OpenMP pode ser de grande ajuda. Considere o código da Listagem 6.

Lista 6. Processamento sequencial em um loop for
int main( )
{
int a[1000000], b[1000000]; 
// ... some initialization code for populating arrays a and b; 
int c[1000000];
for (int i = 0; i < 1000000; ++i)
  c[i] = a[i] * b[i] + a[i-1] * b[i+1];
// ... now do some processing with array c
 }

Claramente, é possível dividir o loop for e executar em mais de um núcleo. O cálculo de qualquer c[k] é independente dos outros elementos do array c. A Listagem 7 mostra como o OpenMP ajuda a fazer isso.

Lista 7. Processamento paralelo em um loop for com o pragma parallel for
int main( )
{
int a[1000000], b[1000000]; 
// ... some initialization code for populating arrays a and b; 
int c[1000000];
#pragma omp parallel for 
for (int i = 0; i < 1000000; ++i)
  c[i] = a[i] * b[i] + a[i-1] * b[i+1];
// ... now do some processing with array c
 }

O pragma parallel for ajuda a dividir a carga de trabalho do loop for em mais de um encadeamento. Cada encadeamento pode ser executado em um núcleo diferente, o que reduz significativamente o tempo total de cálculo. A Listagem 8 prova isso.

Lista 8. Entendendo omp_get_wtime
#include <omp.h>
#include <math.h>
#include <time.h>
#include <iostream>

int main(int argc, char *argv[]) {
    int i, nthreads;
    clock_t clock_timer;
    double wall_timer;
    double c[1000000]; 
    for (nthreads = 1; nthreads <=8; ++nthreads) {
        clock_timer = clock();
        wall_timer = omp_get_wtime();
        #pragma omp parallel for private(i) num_threads(nthreads)
        for (i = 0; i < 1000000; i++) 
          c[i] = sqrt(i * 4 + i * 2 + i); 
        std::cout << "threads: " << nthreads <<  " time on clock(): " << 
            (double) (clock() - clock_timer) / CLOCKS_PER_SEC
           << " time on wall: " <<  omp_get_wtime() - wall_timer << "\n";
    }
}

Na Listagem 8, o código aumenta continuamente o número de encadeamentos para medir quanto tempo o loop for interno leva para ser executado. A API omp_get_wtime retorna o tempo em segundos a partir de algum ponto arbitrário, porém consistente. Portanto, omp_get_wtime() - wall_timer retorna o tempo real para execução do loop for. A chamada do sistema clock() é usada para estimar o tempo de uso do processador para todo o programa — ou seja, o tempo de uso de processador dos encadeamentos individuais é somado antes de a chamada informar o número final. No meu computador Intel Core i7, a Listagem 9 mostra as informações exibidas.

Lista 9. Números para a execução do loop for interno
threads: 1 time on clock(): 0.015229 time on wall: 0.0152249
threads: 2 time on clock(): 0.014221 time on wall: 0.00618792
threads: 3 time on clock(): 0.014541 time on wall: 0.00444412
threads: 4 time on clock(): 0.014666 time on wall: 0.00440478
threads: 5 time on clock(): 0.01594 time on wall: 0.00359988
threads: 6 time on clock(): 0.015069 time on wall: 0.00303698
threads: 7 time on clock(): 0.016365 time on wall: 0.00258303
threads: 8 time on clock(): 0.01678 time on wall: 0.00237703

Embora o tempo de processador seja quase o mesmo em todas as execuções (como deveriam ser, exceto por algum tempo adicional para criar os encadeamentos e o comutador de contexto), o que nos interessa é o tempo real. Ele é reduzido progressivamente à medida que o número de encadeamentos aumenta, dando a entender que os dados estão sendo calculados pelos núcleos em paralelo. Uma nota final sobre a sintaxe do pragma: #pragma parallel for private(i) significa que a variável de loop i deve ser tratada como um armazenamento local de encadeamento, com cada encadeamento tendo uma cópia da variável. A variável local do encadeamento não é inicializada.


Seções críticas com OpenMP

Você não achou que o OpenMP ia cuidar das seções críticas sozinhos, achou? Claro, não é necessário criar explicitamente uma mutex, mas ainda é preciso especificar a seção crítica. Aqui está a sintaxe:

#pragma omp critical (optional section name)
{
// no 2 threads can execute this code block concurrently
}

O código que vem depois de pragma omp critical pode apenas ser executado por um único encadeamento em um dado momento. Além disso, optional section name é um identificador global, e dois encadeamentos não podem executar seções críticas com o mesmo identificador global ao mesmo tempo. Considere o código da Listagem 10.

Lista 10. Mais de uma seção crítica com o mesmo nome
#pragma omp critical (section1)
{
myhashtable.insert("key1", "value1");
} 
// ... other code follows
#pragma omp critical (section1)
{
myhashtable.insert("key2", "value2");
}

Com base nesse código, podemos supor com segurança que as duas inserções de hashtable nunca acontecerão simultaneamente, pois os nomes da seção crítica são os mesmos. Isso é um pouco diferente da maneira com que você está acostumado a lidar com seções críticas usando pthreads, que são, em grande parte, caracterizadas pelo uso (ou abuso) de bloqueios.

Bloqueios e mutexes com OpenMP

Curiosamente, OpenMP tem suas próprias versões de mutexes (então não se trata apenas de pragmas). Seja bem-vindo ao omp_lock_t, definido como parte do arquivo de cabeçalho omp.h. As operações de mutex no estilo pthread são iguais — até os nomes das APIs são semelhantes. Há cinco APIs que o desenvolvedor deve conhecer:

  • omp_init_lock: Essa deve ser a primeira API a acessar omp_lock_t. É usada para inicialização. Observe que, logo após a inicialização, considera-se que o bloqueio não foi definido.
  • omp_destroy_lock: Essa API destrói o bloqueio. O bloqueio deve estar no estado não definido para que essa API seja chamada, o que significa que não é possível chamar omp_set_lock e depois fazer uma chamada para destruir o bloqueio.
  • omp_set_lock: Essa API define omp_lock_t— ou seja, o mutex é adquirido. Se um encadeamento não puder definir o bloqueio, ele continuará a aguardar até que possa.
  • omp_test_lock: Essa API tenta bloquear se o bloqueio estiver disponível, e retorna 1 em caso de sucesso e 0 em caso de fracasso. Essa é uma API sem bloqueio — ou seja, essa função não faz o encadeamento aguardar para definir o bloqueio.
  • omp_unset_lock: Essa API libera o bloqueio.

A Listagem 11 mostra uma implementação trivial de uma fila legada de um encadeamento estendida para lidar com multiencadeamento usando bloqueios do OpenMP. Observe que isso pode não ser o ideal para todas as situações. O exemplo é apenas uma ilustração rápida.

Lista 11. Usando OpenMP para estender uma fila de um encadeamento
#include <openmp.h> 
#include "myqueue.h"

class omp_q : public myqueue<int> { 
public: 
   typedef myqueue<int> base; 
   omp_q( ) { 
      omp_init_lock(&lock);
   }
   ~omp_q() { 
       omp_destroy_lock(&lock);
   }
   bool push(const int& value) { 
      omp_set_lock(&lock);
      bool result = this->base::push(value);
      omp_unset_lock(&lock);
      return result;
   }
   bool trypush(const int& value) 
   { 
       bool result = omp_test_lock(&lock);
       if (result) {
          result = result && this->base::push(value);
          omp_unset_lock(&lock);
      } 
      return result;
   }
   // likewise for pop 
private: 
   omp_lock_t lock;
};

Bloqueios aninhados

Outros tipos de bloqueios fornecidos pelo OpenMP são variações do omp_nest_lock_t. São semelhantes a omp_lock_t, com a vantagem de que podem ser bloqueados várias vezes pelo encadeamento que já está realizando o bloqueio. Cada vez que o bloqueio aninhado é readquirido pelo encadeamento que o contém usando omp_set_nest_lock, um contador interno é aumentado. O bloqueio é liberado pelo encadeamento quando uma ou mais chamadas para omp_unset_nest_lock finalmente reconfiguram o contador do bloqueio interno para 0. Aqui estão as APIs usadas para omp_nest_lock_t:

  • omp_init_nest_lock(omp_nest_lock_t* ): Essa API inicializa o contador de aninhamento interno para 0.
  • omp_destroy_nest_lock(omp_nest_lock_t* ): Essa API destrói o bloqueio. Um chamado para essa API em um bloqueio com contagem de aninhamento interno diferente de zero resulta em comportamento indefinido.
  • omp_set_nest_lock(omp_nest_lock_t* ): Essa API é semelhante a omp_set_lock, mas o encadeamento pode chamar a função mais de uma vez enquanto mantém o bloqueio.
  • omp_test_nest_lock(omp_nest_lock_t* ): Essa API é uma versão sem bloqueio de omp_set_nest_lock.
  • omp_unset_nest_lock(omp_nest_lock_t* ): Essa API libera o bloqueio quando o contador interno é 0. Do contrário, o contador é diminuído com cada chamada para esse método.

Controle de baixa granularidade sobre a execução de tarefas

Você já viu que todos os encadeamentos executam o bloco de códigos após pragma omp parallel em paralelo. É possível categorizar ainda mais o código dentro desse bloco para ser executado por encadeamentos selecionados. Considere o código da Listagem 12.

Lista 12. Aprendendo a usar o pragma de seções paralelas
int main( )
{
  #pragma omp parallel
  {
    cout << "All threads run this\n";
    #pragma omp sections
    {
      #pragma omp section
      {
        cout << "This executes in parallel\n";
      }
      #pragma omp section
      {
        cout << "Sequential statement 1\n";
        cout << "This always executes after statement 1\n";
      }
      #pragma omp section
      {
        cout << "This also executes in parallel\n";
      }
    }
  }
}

O código que vem antes de pragma omp sections, mas logo após pragma omp parallel, é executado por todos os encadeamentos em paralelo. O bloco que vem depois de pragma omp sections é classificado ainda mais em subseções individuais usando pragma omp section. Cada bloco pragma omp section está disponível para ser executado por um encadeamento individual. No entanto, as instruções individuais dentro do bloco de seção são sempre executadas em sequência. A Listagem 13 mostra a saída do código da Listagem 12.

Lista 13. Saída do código da Listagem 12
tintin$ ./a.out 
All threads run this
All threads run this
All threads run this
All threads run this
All threads run this
All threads run this
All threads run this
All threads run this
This executes in parallel
Sequential statement 1
This also executes in parallel
This always executes after statement 1

Na Listagem 13, temos novamente oito encadeamento sendo criados inicialmente. Desses oito encadeamentos, há trabalho suficiente para apenas três deles no bloco pragma omp sections. Na segunda seção, especificamos a ordem na qual as instruções de impressão são executadas. É esse o motivo para usar o pragma sections. Se for necessário, é possível especificar a ordem dos blocos de códigos.


Entendendo as diretivas firstprivate e lastprivate em conjunto com loops paralelos

Anteriormente, você viu o uso de private para declarar o armazenamento local de encadeamentos. Então como inicializar as variáveis locais de encadeamento? Talvez sincronizá-las com a variável no encadeamento principal antes de continuar com as operações? É nessa situação que a diretiva firstprivate é útil.

A diretiva firstprivate

Ao usar firstprivate(variable), é possível inicializar a variável em um encadeamento para o valor que ela tinha em main. Considere o código da Listagem 14.

Lista 14. Usando a variável local do encadeamento que não está sincronizada com o encadeamento principal
#include <stdio.h>
#include <omp.h>

int main()
{
  int idx = 100;
  #pragma omp parallel private(idx)
  {
    printf("In thread %d idx = %d\n", omp_get_thread_num(), idx);
  }
}

Aqui está a saída que eu recebi. Seus resultados podem ser diferentes.

In thread 1 idx = 1
In thread 5 idx = 1
In thread 6 idx = 1
In thread 0 idx = 0
In thread 4 idx = 1
In thread 7 idx = 1
In thread 2 idx = 1
In thread 3 idx = 1

A Listagem 15 mostra o código com a diretiva firstprivate. A saída, como era esperado, imprime idx inicializado para 100 em todos os encadeamentos.

Lista 15. Usando a diretiva firstprivate para inicializar as variáveis locais do encadeamento
#include <stdio.h>
#include <omp.h>

int main()
{
  int idx = 100;
  #pragma omp parallel firstprivate(idx)
  {
    printf("In thread %d idx = %d\n", omp_get_thread_num(), idx);
  }
}

Observe também que usamos o método omp_get_thread_num( ) para acessar o ID de um encadeamento. Isso é diferente do ID de encadeamento que o comando top do Linux® mostra. Esse esquema é apenas uma maneira de o OpenMP acompanhar as contagens de encadeamento. Outra nota sobre a diretiva firstprivate, caso você queira usá-la no seu código C++: a variável que a diretiva firstprivate usa é um construtor de cópias para inicializar-se a partir da variável do encadeamento principal. Portanto, ter um construtor de cópias privado em uma classe invariavelmente resulta em coisas ruins. Vamos agora passar para a diretiva lastprivate, que é, de certa forma, o reverso da moeda.

A diretiva lastprivate

Em vez de inicializar uma variável local de encadeamento com os dados do encadeamento principal, agora queremos sincronizar os dados do encadeamento principal com aqueles gerados pelo último loop executado. O código na Listagem 16 executa um loop for paralelo.

Lista 16. Usando um loop for paralelo sem sincronização de dados com o encadeamento principal
#include <stdio.h>
#include <omp.h>

int main()
{
  int idx = 100;
  int main_var = 2120;

  #pragma omp parallel for private(idx) 
  for (idx = 0; idx < 12; ++idx)
  {
    main_var = idx * idx;
    printf("In thread %d idx = %d main_var = %d\n",
      omp_get_thread_num(), idx, main_var);
  }
  printf("Back in main thread with main_var = %d\n", main_var);
}

No meu computador de desenvolvimento, com oito núcleos, o OpenMP cria seis encadeamentos para o bloco parallel for. Cada encadeamento, por sua vez, é responsável por duas iterações do loop. O valor final de main_var depende do último encadeamento executado e, portanto, o valor de idx nesse encadeamento. Em outras palavras, o valor de main_var não depende do último valor de idx, mas no valor de idx no encadeamento executado por último. O código na Listagem 17 mostra isso.

Lista 17. O valor de main_var depende do último encadeamento executado
In thread 4 idx = 8 main_var = 64
In thread 2 idx = 4 main_var = 16
In thread 5 idx = 10 main_var = 100
In thread 3 idx = 6 main_var = 36
In thread 0 idx = 0 main_var = 0
In thread 1 idx = 2 main_var = 4
In thread 4 idx = 9 main_var = 81
In thread 2 idx = 5 main_var = 25
In thread 5 idx = 11 main_var = 121
In thread 3 idx = 7 main_var = 49
In thread 0 idx = 1 main_var = 1
In thread 1 idx = 3 main_var = 9
Back in main thread with main_var = 9

Execute o código na Listagem 17 algumas vezes para convencer-se de que o valor de main_var no encadeamento principal sempre depende do valor de idx no último encadeamento executado. E se quisermos sincronizar o valor do encadeamento principal com o valor final de idx no loop? É nessa parte que É aqui que entra a diretiva lastprivate, como mostra a Listagem 18. Assim como no código da Listagem 17, execute o código da Listagem 18 algumas vezes para convencer-se de que o valor final de main_var no encadeamento principal é 121 (idx é o valor do final do contador de loop).

Lista 18. Usando a diretiva lastprivate para sincronização
#include <stdio.h>
#include <omp.h>

int main()
{
  int idx = 100;
  int main_var = 2120;

  #pragma omp parallel for private(idx) lastprivate(main_var)
  for (idx = 0; idx < 12; ++idx)
  {
    main_var = idx * idx;
    printf("In thread %d idx = %d main_var = %d\n",
      omp_get_thread_num(), idx, main_var);
  }
  printf("Back in main thread with main_var = %d\n", main_var);
}

A Listagem 19 mostra a saída da Listagem 18.

Lista 19. Saída do código na Listagem 18 (observe que o valor de main_var always é 121 no encadeamento principal)
In thread 3 idx = 6 main_var = 36
In thread 2 idx = 4 main_var = 16
In thread 1 idx = 2 main_var = 4
In thread 4 idx = 8 main_var = 64
In thread 5 idx = 10 main_var = 100
In thread 3 idx = 7 main_var = 49
In thread 0 idx = 0 main_var = 0
In thread 2 idx = 5 main_var = 25
In thread 1 idx = 3 main_var = 9
In thread 4 idx = 9 main_var = 81
In thread 5 idx = 11 main_var = 121
In thread 0 idx = 1 main_var = 1
Back in main thread with main_var = 121

Uma observação final: para que um objeto C++ tenha suporte para o operador lastprivate, é necessário que o método operator= esteja disponível publicamente na classe correspondente.


Ordenação por intercalação (merge sort) com OpenMP

Vamos observar um exemplo prático no qual o conhecimento sobre OpenMP ajuda a economizar tempo de execução. Essa não é uma versão muito otimizada do algoritmo merge sort, mas é o suficiente para mostrar as vantagens de usar OpenMP no código. A Listagem 20 mostra o código de exemplo.

Lista 20. Ordenação por intercalação usando OpenMP
#include <omp.h>
#include <vector>
#include <iostream>
using namespace std;

vector<long> merge(const vector<long>& left, const vector<long>& right)
{
    vector<long> result;
    unsigned left_it = 0, right_it = 0;

    while(left_it < left.size() && right_it < right.size())
    {
        if(left[left_it] < right[right_it])
        {
            result.push_back(left[left_it]);
            left_it++;
        }
        else					
        {
            result.push_back(right[right_it]);
            right_it++;
        }
    }

    // Push the remaining data from both vectors onto the resultant
    while(left_it < left.size())
    {
        result.push_back(left[left_it]);
        left_it++;
    }

    while(right_it < right.size())
    {
        result.push_back(right[right_it]);
        right_it++;
    }

    return result;
}

vector<long> mergesort(vector<long>& vec, int threads)
{
    // Termination condition: List is completely sorted if it
    // only contains a single element.
    if(vec.size() == 1)
    {
        return vec;
    }

    // Determine the location of the middle element in the vector
    std::vector<long>::iterator middle = vec.begin() + (vec.size() / 2);

    vector<long> left(vec.begin(), middle);
    vector<long> right(middle, vec.end());

    // Perform a merge sort on the two smaller vectors

    if (threads > 1)
    {
      #pragma omp parallel sections
      {
        #pragma omp section
        {
          left = mergesort(left, threads/2);
        }
        #pragma omp section
        {
          right = mergesort(right, threads - threads/2);
        }
      }
    }
    else
    {
      left = mergesort(left, 1);
      right = mergesort(right, 1);
    }

    return merge(left, right);
}

int main()
{
  vector<long> v(1000000);
  for (long i=0; i<1000000; ++i)
    v[i] = (i * i) % 1000000;
  v = mergesort(v, 1);
  for (long i=0; i<1000000; ++i)
    cout << v[i] << "\n";
}

Usando oito encadeamentos para executar esse merge sort, a duração do tempo de execução que eu obtive foi de 2,1 segundos, enquanto um único encadeamento deu 3,7 segundos. A única coisa que se deve lembrar aqui é que é preciso ter cuidado com o número de encadeamentos. Eu comecei com oito encadeamentos. Sua experiência pode ser diferente, de acordo com a configuração do seu sistema. No entanto, sem a contagem explícita de encadeamentos, você acabaria criando centenas, senão milhares, deles, com altas chances de que o desempenho do sistema decaísse. Além disso, o pragma sections, discutido anteriormente, foi bem utilizado com o código merge sort.


Conclusão

Assim acaba o artigo. Nós avançamos bastante aqui: você conheceu os pragmas paralelos do OpenMP; aprendeu diferentes maneiras de criar encadeamentos; ficou convencido das melhorias em desempenho de tempo, sincronização e controle de baixa granularidade que o OpenMP oferece; e terminou com uma aplicação prática do OpenMP com merge sort. Mas ainda há muito para estudar, é o melhor lugar para isso é o site do projeto OpenMP. Não deixe de consultar a seção Recursos para mais detalhes.

Recursos

Aprender

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=839212
ArticleTitle=Aprendendo a usar a estrutura OpenMP com GCC
publish-date=10082012