parelalização de aplicações com openmp

5
TEMA DA CAPA 6 Introdução O OpenMP é uma norma/API para programação paralela em sistemas de memória partilhada para as linguagens de programação C, C++ e Fortran, desenvolvida e mantida pelo OpenMP Architecture Review Board. Disponibiliza uma alternativa simples e portável a soluções de mais baixo nível como POSIX Threads, e é suportado por vários compiladores como o GCC ou ICC, e deverá chegar em breve ao Clang/LLVM. O OpenMP distingue-se de outras soluções para suporte a paralelismo em memória partilhada pelo seu nível de abstracção, na medida em que o essencial das suas funcionalidades é obtido através de um conjunto de directivas do compilador que especificam de forma declarativa como é que diferentes partes do código podem ser executadas em paralelo. Adicionalmente, um compilador que não suporte OpenMP pode simplesmente ignorar estas directivas, continuando a aplicação a funcionar correctamente (embora de forma sequencial). Contudo, aplicações mais complexas podem também necessitar de chamadas a funções de mais baixo nível (disponibilizados pela API do OpenMP), e que tornam a compilação da aplicação dependente do OpenMP. Neste artigo pretende-se mostrar como podem explorar paralelismo em memória partilhada recorrendo a um pequeno conjunto de funcionalidades do OpenMP, de modo a tirarem partido dos processadores com múltiplos núcleos disponíveis na generalidade dos PCs hoje em dia. Utilizar-se -á o C com linguagem de suporte neste artigo. Os programas serão compilados usando o GCC 4.7.2. Conceitos Básicos O OpenMP usa threads para obter paralelismo. Todas as threads usam o mesmo espaço de endereçamento de memória, sendo que os dados podem ser partilhados por todas as threads, ou privados de cada thread (cada thread tem uma cópia da variável à qual só ela acede). De notar que o OpenMP utiliza um modelo de memória de consistência relaxada, na medida em que cada threads pode ter uma cópia local temporária (por exemplo, em registos ou cache) cujo valor não necessita de ser constantemente consistente com o valor global visto por todas as outras threads (vários mecanismos são disponibilizados para que o programador possa forçar a consistência de memória em determinados pontos da aplicação). Paralelização de Aplicações com OpenMP É usado um modelo fork & join, em que temos inicialmente uma única thread (a master, ou principal), e depois temos pontos da aplicação em que um grupo de threads é criado (fork, no início de uma região paralela), e pontos da aplicação onde as threads são terminadas voltando a haver apenas uma thread (join, no final de uma região paralela), como mostrado na Figura 1. Ou seja, as regiões paralelas identificam os blocos de código onde o trabalho a ser executado é atribuído a várias threads. A norma do OpenMP prevê a possibilidade de termos regiões paralelas aninhadas, em que cada thread de um grupo de threads dá origem a um novo grupo de threads. No entanto, nem todas as implementações suportam regiões paralelas (e neste caso, cada região paralela aninhada será apenas executada por uma thread). Figura 1: modelo fork & join do OpenMP O acesso às funcionalidades do OpenMP é feito através de três tipos de mecanismos: directivas de compilador permitem criar regiões paralelas, distribuir o trabalho a ser realizado pelas várias threads, controlar o acesso a dados (especificando se são privados ou partilhados, por exemplo), assim como gerir a sincronização entre threads; funções permitem definir o número de threads a usar, obter informações sobre quantas threads estão a correr ou qual o identificador da thread em que estamos, determinar se se está numa zona paralela, gerir locks, aceder a funções de tempo (relógio), entre outras coisas; variáveis de ambiente permitem também definir o número de threads a usar, controlar o escalonamento, definir o máximo aninhamento de regiões paralelas, etc. Nas próximas secções veremos como alguns alguns destes mecanismos podem ser usados, sendo dado especial foco às directivas de compilador.

Upload: amilcarsoares

Post on 08-Nov-2015

11 views

Category:

Documents


4 download

DESCRIPTION

Artigo da Revista ProgramaçãoAutor: Rui Carlos GonçalvesData: Setembro 2014

TRANSCRIPT

  • TEMA DA CAPA

    6

    Introduo

    O OpenMP uma norma/API para programao paralela em

    sistemas de memria partilhada para as linguagens de

    programao C, C++ e Fortran, desenvolvida e mantida

    pelo OpenMP Architecture Review Board. Disponibiliza uma

    alternativa simples e portvel a solues de mais baixo nvel

    como POSIX Threads, e suportado por vrios

    compiladores como o GCC ou ICC, e dever chegar em

    breve ao Clang/LLVM.

    O OpenMP distingue-se de outras solues para suporte a

    paralelismo em memria partilhada pelo seu nvel de

    abstraco, na medida em que o essencial das suas

    funcionalidades obtido atravs de um conjunto de

    directivas do compilador que especificam de forma

    declarativa como que diferentes partes do cdigo podem

    ser executadas em paralelo. Adicionalmente, um compilador

    que no suporte OpenMP pode simplesmente ignorar estas

    directivas, continuando a aplicao a funcionar

    correctamente (embora de forma sequencial). Contudo,

    aplicaes mais complexas podem tambm necessitar de

    chamadas a funes de mais baixo nvel (disponibilizados

    pela API do OpenMP), e que tornam a compilao da

    aplicao dependente do OpenMP.

    Neste artigo pretende-se mostrar como podem explorar

    paralelismo em memria partilhada recorrendo a um

    pequeno conjunto de funcionalidades do OpenMP, de modo

    a tirarem partido dos processadores com mltiplos ncleos

    disponveis na generalidade dos PCs hoje em dia. Utilizar-se

    - o C com linguagem de suporte neste artigo. Os programas

    sero compilados usando o GCC 4.7.2.

    Conceitos Bsicos

    O OpenMP usa threads para obter paralelismo. Todas

    as threads usam o mesmo espao de endereamento de

    memria, sendo que os dados podem ser partilhados por

    todas as threads, ou privados de

    cada thread (cada thread tem uma cpia da varivel qual

    s ela acede). De notar que o OpenMP utiliza um modelo de

    memria de consistncia relaxada, na medida em que

    cada threads pode ter uma cpia local temporria (por

    exemplo, em registos ou cache) cujo valor no necessita de

    ser constantemente consistente com o valor global visto por

    todas as outras threads (vrios mecanismos so

    disponibilizados para que o programador possa forar a

    consistncia de memria em determinados pontos da

    aplicao).

    Paralelizao de Aplicaes com OpenMP

    usado um modelo fork & join, em que temos inicialmente

    uma nica thread (a master, ou principal), e depois temos

    pontos da aplicao em que um grupo de threads criado

    (fork, no incio de uma regio paralela), e pontos da

    aplicao onde as threads so terminadas voltando a haver

    apenas uma thread (join, no final de uma regio paralela),

    como mostrado na Figura 1. Ou seja, as regies paralelas

    identificam os blocos de cdigo onde o trabalho a ser

    executado atribudo a vrias threads. A norma

    do OpenMP prev a possibilidade de termos regies

    paralelas aninhadas, em que cada thread de um grupo

    de threads d origem a um novo grupo de threads. No

    entanto, nem todas as implementaes suportam regies

    paralelas (e neste caso, cada regio paralela aninhada ser

    apenas executada por uma thread).

    Figura 1: modelo fork & join do OpenMP

    O acesso s funcionalidades do OpenMP feito atravs de

    trs tipos de mecanismos:

    directivas de compilador

    permitem criar regies paralelas, distribuir o trabalho a ser

    realizado pelas vrias threads, controlar o acesso a dados

    (especificando se so privados ou partilhados, por exemplo),

    assim como gerir a sincronizao entre threads;

    funes

    permitem definir o nmero de threads a usar, obter

    informaes sobre quantas threads esto a correr ou qual o

    identificador da thread em que estamos, determinar se se

    est numa zona paralela, gerir locks, aceder a funes de

    tempo (relgio), entre outras coisas;

    variveis de ambiente

    permitem tambm definir o nmero de threads a usar,

    controlar o escalonamento, definir o mximo aninhamento de

    regies paralelas, etc.

    Nas prximas seces veremos como alguns alguns destes

    mecanismos podem ser usados, sendo dado especial foco

    s directivas de compilador.

  • TEMA DA CAPA

    7

    Mecanismo do OpenMP

    Nesta seco veremos como podemos usar as

    funcionalidades do OpenMP. O exemplos disponibilizados

    podem ser compilados usando a opo de compilao -

    fopenmp do GCC (caso estejam a usar outro compilador,

    devem recorrer documentao do mesmo para determinar

    como activar as funcionalidades do OpenMP).

    Criao de Threads

    Na base do OpenMP est a directiva omp parallel. Esta

    directiva cria uma regio paralela, i.e., indica que um

    determinado bloco de cdigo ser executada por

    vrias threads (caso o OpenMP esteja activo). Como

    exemplo, considere o seguinte cdigo.

    Caso execute este cdigo (compilado com a opo

    do OpenMP activa), dever ver a mensagem Hello

    world! aparecer no ecr tantas vezes quanto o nmero de

    ncleos do CPU da sua mquina. Tendo em conta que no

    h sincronizao entre as threads, possvel que o texto

    impresso pelas vrias threads aparea misturado.

    Aquilo que executado em paralelo delimitado pelo bloco

    que se segue directiva omp parallel. Por exemplo, no

    seguinte cdigo, temos mensagens que so impressas por

    todas as threads, e mensagens que so impressas apenas

    pela thread principal.

    De notar que a mensagem Only master., que apenas im-

    pressa pela thread principal, dever ser sempre a ltima

    mensagem a ser mostrada, na medida em h uma barreira

    implcita no final do bloco paralelo (a generalidade dos cons-

    trutores tm barreiras implcitas no final, sendo que alguns

    mecanismos suportam a opo nowait para evitar estas bar-

    reiras; consulte a documentao para mais detalhes), i.e.,

    a thread principal no avana enquanto ainda hou-

    ver threads a executar a regio paralela.

    Existem diversas formas de controlar explicitamente o nme-

    ro de threads que so criadas por uma regio paralela. Por

    exemplo, podem simplesmente acrescentar a op-

    o num_threads(3) no final do omp parallel (ficando

    com #pragma omp parallel num_threads(3)) para indicar que

    querem 3 threads.

    Distribuio de Trabalho

    O exemplo anterior no particularmente til na maior parte

    dos casos, na medida em que todas as threads executam o

    mesmo trabalho de forma redundante. Em exemplos reais,

    normalmente o que queremos que cada thread execute

    uma parte diferente do trabalho. O OpenMP disponibiliza

    diversos mecanismos para tal.

    Uma forma simples de distribuir trabalho indicar que dife-

    rentes partes da regio paralela podem ser executadas em

    paralelo, mas cada parte s dever ser executada por

    uma thread. Para obter este comportamento podemos usar a

    directiva omp sections. Considere o seguinte bloco de cdi-

    go.

    Neste bloco de cdigo teremos uma thread a execu-

    tar tast11 e task12, outra thread a executar task2, ou-

    tra thread a executar task3, e todas as threads a execu-

    tar taskAll (visto que esta funo j se encontra fora do blo-

    co omp sections). Ou seja, a directiva omp sections define

    um conjunto de blocos de cdigo em que cada um deles

    deve ser executado por apenas uma thread. Cada um dos

    blocos desse conjunto identificado pela directiva omp secti-

    on. Se houver mais blocos do que threads, uma mes-

    ma thread ir executar mais do que uma seco (reala-se,

    contudo, que a diviso de trabalho entre as threads nem

    sempre equilibrada, podendo uma thread ficar com a maio-

    ria das tarefas). No final do bloco omp sections existe mais

    uma vez uma barreira implcita aplicada a todas as threads.

    Uma outra forma muito til de distribuir trabalho en-

    tre threads dividindo as iteraes de um ciclo for. Para tal

    temos a directiva omp for, usada no prximo exemplo.

    PARALELIZAO DE APLICAES COM OPENMP

    int main(void) { #pragma omp parallel { printf("Hello world!\n"); } return 0; }

    int main(void) { #pragma omp parallel { printf("Hello world!\n"); // todas as threads printf("All threads.\n"); // todas as threads } printf("Only master.\n"); // apenas thread //principal return 0; }

    #pragma omp parallel { #pragma omp sections { #pragma omp section { task11(); // apenas uma thread task12(); // apenas uma thread } #pragma omp section task2(); // apenas uma thread #pragma omp section task3(); // apenas uma thread } taskAll(); // todas as threads }

    double nums[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel for for(int i = 0; i < 10; i++) { nums[i] = sqrt(nums[i]); }

  • TEMA DA CAPA

    8

    Este cdigo computa a raiz quadrada dos elementos de

    um array. As iteraes no tm dependncias, pelo que po-

    demos ter as vrias iteraes a serem executados em para-

    lelo. precisamente isso que a directiva omp for est a fa-

    zer. De referir que estamos a juntar as directivas omp paral-

    lel e omp for numa s, na medida em que ambas se aplicam

    ao mesmo bloco de cdigo.

    Mas nem sempre as iteraes do ciclo so completamente

    independentes, o que pode requerer a utilizao de funcio-

    nalidades mais complexas do OpenMP. Por exemplo, supo-

    nhamos que se pretende somar todos os elementos de

    um array. Tal pode ser feito de forma sequencial com o se-

    guinte cdigo.

    Todas as iteraes escrevem na varivel sum, pelo que no

    caso de executarmos vrias iteraes em paralelo, teremos

    um race condition, que pode levar a resultados incorrectos.

    Existem vrias maneiras de contornar este problema, nome-

    adamente atravs da utilizao de diferentes variveis para

    cada thread, como veremos mais adiante. Contudo,

    o OpenMP disponibiliza um mecanismo de alto nvel para

    este tipo de situaes, em que feita uma operao de re-

    duo de um array (i.e., os elementos do array so agrupa-

    dos num s elemento, neste caso, a soma de todos os ele-

    mentos). O mecanismo em causa o reduction(:).

    Este mecanismo torna a varivel privada a ca-

    da thread dentro da regio paralela (na generalidade dos

    casos, variveis declaradas fora do bloco so partilhadas por

    omisso), e no final realiza uma juno de todas as cpias

    privadas da varivel aplicando a operao . Para o nos-

    so caso, queremos que a varivel sum seja privada a ca-

    da thread, e queremos que no final todas as cpias privadas

    sejam somadas. Assim, a verso paralela pode ser obtida

    como exemplificado no seguinte cdigo.

    Existem formas mais rudimentares de distribuir o trabalho

    por threads, usando a API do OpenMP. Pegando no exem-

    plo anterior, a sua verso paralela pode ser tambm obtida

    atravs do cdigo que se segue.

    Neste exemplo temos novamente todas as threads a execu-

    tar a regio paralela. Aqui a distribuio do trabalho feita

    recorrendo API do OpenMP. Obtendo o identificador de

    cada thread, e o nmero de threads a serem executados,

    podemos usar as estruturas condicionais para que ca-

    da thread execute trabalho diferente. No caso, for(int i = id; i

    < 100; i += nthreads) faz com que a thread id some os valo-

    res dos ndices i tal que i % nthreads == id (sendo nthreads o

    nmero de threads a executar). Desta forma, temos as vrias

    posies do array divididas, de forma cclica, por todas

    as threads. A varivel sumt est declarada dentro da regio

    paralela, pelo que a mesma local a cada thread. No final,

    todas as threads adicionam a sua soma varivel sum, que

    contm a soma total. Mais uma vez necessrio cuidado

    com as race conditions, visto que todas as threads escrevem

    na varivel sum. A directiva omp critical usada para evitar

    problemas. Esta garante que apenas uma thread est a exe-

    cutar aquele bloco da regio paralela num determinado mo-

    mento. De notar tambm que o acesso s funes

    da API do OpenMP requer a utilizao do header omp.h.

    Penso que todos os leitores iro concordar que a utilizao

    do omp for e do reduction permite um cdigo muito mais

    simples, e a verso que se recomenda que seja utilizada.

    Por norma, apenas se recomenda fazer este tipo de diviso

    de trabalho manual quando no possvel faz-lo usando

    apenas as directivas do compilador (o que pode ocorrer em

    casos mais complexos).

    Sincronizao de Threads

    No exemplo anterior j vimos como podemos sincronizar a

    execuo de threads atravs do omp critical. Existem mais

    alguns mecanismos que podem ser usados para sincroniza-

    o:

    omp atomic

    PARALELIZAO DE APLICAES COM OPENMP

    double sum = 0; double nums[SIZE]; // ... // inicializar nums // ... for(int i = 0; i < SIZE; i++) { sum += nums[i]; }

    double sum = 0; double nums[SIZE]; // ... // inicializar nums // ... #pragma omp parallel for reduction(+:sum) for(int i = 0; i < SIZE; i++) { sum += nums[i]; }

    // varivel que ir a double sum = 0; double nums[SIZE]; // ... // inicializar nums // ... // criao de uma regio paralela #pragma omp parallel { // declarada dentro da regio paralela, logo privada thread double sumt = 0; // obter o id desta thread int id = omp_get_thread_num(); // obter o nmero de threads int nthreads = omp_get_num_threads(); for(int i = id; i < 100; i += nthreads) { sumt += nums[i]; } // regio crtica; apenas uma thread a executa em cada momento #pragma omp critical sum += sumt; }

  • TEMA DA CAPA

    9

    uma verso mais bsica (mas tambm mais eficiente)

    do omp critical, que permite tornar as actualizaes de vari-

    veis atmicas (no exemplo anterior seria mais eficiente utili-

    zar o omp atomic do que o omp critical);

    omp master

    indica que um bloco de cdigo s ser executado pe-

    la thread principal (de referir que, ao contrrio da generalida-

    de dos mecanismos do OpenMP, no existe uma barreira

    implcita no final destes blocos);

    omp barrier

    permite definir barreiras explcitas, onde as threads de uma

    regio bloqueiam at que todas elas tenham atingido aquele

    ponto de execuo;

    omp flush

    usado para garantir que todas as threads tm uma viso

    consistente da memria naquele ponto (como indicado ante-

    riormente, o OpenMP usa um modelo de memria de consis-

    tncia relaxada);

    omp ordered

    permite definir que um determinado bloco dentro de um ciclo

    que est a correr em paralelo dever ser executado na mes-

    ma ordem em que seria executado se estivesse a correr de

    forma sequencial.

    Adicionalmente, podemos ainda sincronizar a execuo

    de threads atravs das funes de gesto

    de locks disponibilizados pela API do OpenMP, que incluem

    o omp_init_lock (inicializao de

    um lock), omp_set_lock (adquire um lock, esperando at que

    tal seja possvel), omp_unset_lock (liberta

    um lock), omp_test_lock (tenta adquirir um lock, mas no

    bloqueia caso no seja possvel),

    e omp_destroy_lock (destri o lock). Por exemplo, o omp

    critical usado no exemplo anterior podia ser substitudo pela

    utilizao de locks, como mostrado no prximo exemplo.

    Mais uma vez, sempre que possvel, aconselha-se a utiliza-

    o dos mecanismos abstractos disponibiliza-

    das OpenMP (critical, atomic, barrier, etc.) em vez da utiliza-

    o das funes de mais baixo nvel disponibilizados pe-

    la API.

    Para terminar, convm referir que os mecanismos de sincro-

    nizao devem ser evitados, pois os mesmos impli-

    cam overheads, muitas vezes ao ponto impedirem que a

    paralelizao produza qualquer ganho de desempenho. Em

    geral recomendvel que se tente reorganizar o algoritmo

    de modo a evitar a necessidade destes mecanismos.

    Tarefas Paralelas

    Uma outra forma de definir blocos de cdigo que podem ser

    executados em paralelo (por uma thread cada) usando a

    directiva omp task. Esta directiva permite definir tarefas cuja

    a execuo pode ser feita de forma assncrona. A directiva

    de sincronizao omp taskwait deve depois ser usada para

    bloquear a execuo da aplicao at que a tarefa tenha

    sido concluda (quando necessrio). Por exemplo, o seguinte

    cdigo implementa o clculo de um nmero de Fibonacci em

    paralelo.

    Neste cdigo indicado que as chamadas recursivas fun-

    o fibonacci podem ser executadas de forma assncrona.

    Antes de precisarmos de utilizar o resultados dessas chama-

    das recursivas, utilizamos o omp taskwait para esperarmos

    pela concluso das tarefas.

    Neste exemplo podemos tambm ver mais uma vez a utiliza-

    o de mecanismos de controlo de escopo/partilha (j anteri-

    ormente tnhamos visto que o reduction podiam ser usado

    para tornar variveis privadas s threads). Ao contrrio da

    generalidade dos construtores, no omp task as variveis

    declaradas anteriormente so privadas task por omisso.

    Assim, se precisarmos de aceder ao seu valor mais tarde,

    como no caso das variveis fib1 e fib2, necessrio tornar

    as variveis partilhadas. Tal feito atravs das op-

    PARALELIZAO DE APLICAES COM OPENMP

    double sum = 0; double nums[SIZE]; // ... // inicializar nums // ... omp_lock_t sumlock; omp_init_lock(&sumlock); #pragma omp parallel { // declarada dentro da regio paralela, logo //privada thread double sumt = 0; // obter o id desta thread int id = omp_get_thread_num(); // obter o nmero de threads int nthreads = omp_get_num_threads(); for(int i = id; i < SIZE; i += nthreads) { sumt += nums[i]; } omp_set_lock(&sumlock); sum += sumt; omp_unset_lock(&sumlock);

    } omp_destroy_lock(&sumlock);

    int fibonacci(int n) { int fib1, fib2, result; if(n == 0 || n == 1) { result = n; } else { // cria uma tarefa assncrona #pragma omp task shared(fib1) fib1 = fibonacci(n - 1); // cria uma tarefa assncrona #pragma omp task shared(fib2) fib2 = fibonacci(n - 2); // espera pelas tarefas assncronas lanadas #pragma omp taskwait result = fib1 + fib2; } return result; }

  • TEMA DA CAPA

    10

    es shared(fib1) e shared(fib2).

    Concluso

    Neste artigo procurou-se mostrar como podem paralelizar

    aplicaes de forma simples usando o OpenMP. Contudo,

    este disponibiliza apenas uma introduo ao tema, sendo

    que as funcionalidades do OpenMP vo muito para alm das

    aqui abordadas, nomeadamente no que diz respeito ao es-

    calonamento de tarefas, ou s formas de controlo de escopo/

    partilha de variveis. Sugere-se ao leitor interessado que

    consulte a lista de referncias disponibilizada no final caso

    deseje aprofundar os seus conhecimentos.

    Neste artigo tambm no houve grande preocupao com o

    desempenho da aplicao. Tirar partido da paralelizao

    requer cuidados adicionais no que diz respeito performan-

    ce, nomeadamente para se fazer um bom uso da hierarquia

    de memria (por exemplo, para se evitarem problemas

    de false sharing), para se conseguir um bom balanceamento

    de carga, ou para evitar overheads excessivos com sincroni-

    zao (como referido anteriormente).

    Apesar do OpenMP ser destinado a sistemas memria parti-

    lhada, pode ser usado em conjunto com solues de mem-

    ria distribuda (como o MPI). Para alm das linguagens su-

    portadas oficialmente (C, C++ e Fortran), existem soluo

    similares para outras linguagens como o Java, por exemplo.

    Referncias

    Website do OpenMP: http://www.openmp.org/

    Especificao do OpenMP: http://www.openmp.org/mp-

    documents/OpenMP4.0.0.pdf

    Tutorial de OpenMP: https://computing.llnl.gov/tutorials/

    openMP/

    Tuturial prtico de OpenMP: http://openmp.org/mp-

    documents/omp-hands-on-SC08.pdf

    PARALELIZAO DE APLICAES COM OPENMP

    AUTOR

    Escrito Por Rui Carlos Gonalves

    Para alm das lin-

    guagens suportadas

    oficialmente

    (C, C++ e Fortran),

    existem soluo simila-

    res para outras lingua-

    gens como o Java