Usando o Intel® Inspector nos nós acelerados do NCC/UNESP

Daniel Massaru Katsurayama, Jairo Panetta, Simone Shizue Tomita Lima

Finalidade

Este documento relata como utilizar o Intel® Inspector nos nós acelerados por processadores Intel® Xeon Phi™ disponíveis no NCC/UNESP para auxiliar a paralelização de programas utilizando OpenMP*.

OpenMP é reconhecido pela facilidade de uso – talvez seja o mecanismo mais fácil para paralelizar um programa. Entretanto, paralelizar um programa com muitas linhas de código pode ser extremamente difícil. O Intel Inspector é uma ferramenta de software projetada para auxiliar esse processo.

Paralelizar um programa utilizando OpenMP pode inserir condições de corrida (“race conditions”) e/ou travas (“deadlocks”) na execução do programa. Tais imperfeições são difíceis de detectar e a dificuldade aumenta com o número de linhas do código fonte. Por exemplo, suponha um programa composto por um laço que percorre uma estrutura de dados composta por um conjunto de colunas. Suponha que o laço contenha apenas a invocação de um procedimento que aplica um processo físico em uma única coluna – o laço garante que o processo físico será aplicado a todas as colunas do conjunto. Suponha que esse processo físico opere apenas na coluna invocada, independente das outras colunas. Então, em tese, o laço que percorre as colunas pode ser paralelizado por diretivas OpenMP, pois as iterações do laço são, em tese, independentes entre si. Entretanto, se o procedimento invocado no laço for a raiz de longa árvore de procedimentos, é perfeitamente possível que existam variáveis globais (por exemplo, “static” em C ou “common” em Fortran) utilizadas por procedimentos da árvore para comunicar valores entre esses procedimentos. Por exemplo, variáveis globais para comunicar valores intermediários calculados por um procedimento para serem utilizados por outro procedimento e posteriormente descartados. Execuções simultâneas das interações do laço acarretam em execuções simultâneas da árvore de procedimentos, que podem tentar armazenar valores distintos nessas variáveis globais, causando condições de corrida, pois as variáveis globais ocupariam as mesmas posições de memória, fixas para todas as invocações dos procedimentos. Como encontrar tais situações em árvores de procedimentos com centenas de milhares de linhas de código fonte?

O Intel Inspector ajuda a encontrar tais imperfeições, apontando ocorrências de condição de corrida e de travas durante execução monitorada do programa. O uso da ferramenta envolve duas fases: coleta de dados e apresentação de resultados. Na primeira fase, enquanto o programa é executado sob supervisão da ferramenta, a ferramenta gera arquivos contendo traços de execução. Tais arquivos contém, dentre outras informações, detalhes das potenciais condições de corrida e travas. Na segunda fase, após a execução, a ferramenta deve ser novamente invocada para processar os traços de execução e apresentar potenciais corridas e travas. Interface gráfica permite fácil visualização dos problemas encontrados.

Como este trabalho demonstra, o Intel Inspector é ferramenta de grande utilidade para auxiliar a paralelização OpenMP de programas de porte.

Mas não é de qualquer forma a ferramenta perfeita pelo fato de basear sua análise em uma execução com número fixo de threads, pode não detectar problemas que ocorrerão com outro número de threads. Denominamos esses casos “falsos negativos”. Da mesma forma, pode detectar problemas que, teoricamente, produzem condição de corrida mas, praticamente, não as produzem. Por exemplo, duas threads escrevem na mesma posição de memória o mesmo valor como no caso de uma constante. A ferramenta aponta a condição de corrida, mas se todas as threads em uma execução guardarem a mesma aproximação para o valor de π, por exemplo, não haverá erro no programa. Denominamos essas ocorrências como “falsos positivos”.

Este trabalho está estruturado em duas fases. A primeira fase mostra como preparar o programa para utilizar o Intel Inspector e como executá-lo. A segunda fase mostra nossa experiência de uso do Intel Inspector em um código real: a radiação do BRAMS. Explicamos a interface gráfica, mostramos casos que classificamos como condições de corrida reais, casos que são falsos positivos e casos que são falsos negativos. O documento termina com estatísticas sobre o uso da ferramenta.

Como utilizar o Intel® Inspector nos nós acelerados do NCC/UNESP

Chaves de compilação para usar o Intel Inspector e OpenMP

O código escrito em linguagem C/C++/Fortran deve ser compilado com a chave -qopenmp para que o compilador Intel reconheça as diretivas OpenMP. Recomentamos ligar a chave de compilação -g (modo de depuração) para que o Intel Inspector utilize os símbolos do programa (como nome de arquivos, número das linhas do código fonte, nome das variáveis envolvidas) em seus relatórios. É possível utilizar o Inspector sem essa chave. Entretanto, aplicações compiladas e linkeditadas em modo de depuração produzem resultados de análise mais precisos e satisfatórios.

Para aumentar a precisão dos resultados, recomendamos o uso das chaves de compilação -O0 -shared-intel e da chave -check none (somente Intel® Fortran Compiler). Caso a aplicação utilize chamadas a funções da biblioteca MPI (somente Intel® MPI Library), recomendamos que a chave -mt_mpi seja adicionada à linha de compilação/linkedição para uso da versão “thread safe” desta biblioteca. A não utilização destas chaves no procedimento de compilação/linkedição poderá reduzir a qualidade dos resultados das análises, impactando em contratempos durante o uso do Intel Inspector (veja Tabela 1):

Tabela 1: Chaves de compilação e impacto na falta do uso destas chaves

ChaveCompiladorPropósitoImpacto na falta do uso da chave
‑qopenmpC/C++/FortranReconhecimento das diretivas OpenMPErros de compilação, ou obtenção de “falsos negativos” na execução paralela
‑gC/C++/FortranAtivar modo de depuraçãoFalta de informação sobre o arquivo, ou sobre a linha do código
‑O0C/C++/FortranDesabilitar otimizaçãoInformação incorreta sobre o arquivo, ou sobre a linha do código
‑shared‑intelC/C++/FortranUsar biblioteca dinâmica de tempo de execuçãoObtenção de ‘”falsos positivos”, ou falha na localização de trechos do código
‑check noneFortranNão verificar erros em tempo de execuçãoObtenção de “falsos positivos”
‑mt_mpiMPIUsar versão “thread safe” da biblioteca MPIObtenção de “falsos positivos”

A seguir, apresentamos alguns exemplos de uso das chaves de compilação para utilização do Intel Inspector e OpenMP na presença, ou não da biblioteca MPI:

Exemplo 1:
Compilar uma aplicação escrita em C, sem uso da biblioteca MPI:
% icc -qopenmp -O0 -g -shared-intel -o app.x app.c

Exemplo 2:
Compilar uma aplicação escrita em Fortran, sem uso da biblioteca MPI:
% ifort -qopenmp -O0 -g -shared-intel -check none -o app.x app.f90

Exemplo 3:
Compilar uma aplicação escrita em C, com uso da biblioteca Intel MPI Library:
% mpiicc -qopenmp -O0 -g -shared-intel -mt_mpi -o app.x app.c

Exemplo 4:
Compilar uma aplicação escrita em Fortran, com uso da biblioteca Intel MPI Library:
% mpiifort -qopenmp -O0 -g -shared-intel -check none -mt_mpi -o app.x app.f90

Configuração do ambiente de software para uso do Intel Inspector nos nós acelerados

O uso do Intel Inspector requer configurar o ambiente de software para seu uso correto nos nós acelerados. Execute um dos seguintes scripts de configuração da ferramenta para preparar este ambiente:

Usuários bash shell:
% source <inspector-install-dir>/inspxe-vars.sh

Usuários csh/tcsh shell:
% source <inspector-install-dir>/inspxe-vars.csh

Onde:
<inspector-install-dir> é o diretório da instalação do Intel Inspector.

Acrescente a localização dos binários do Intel Inspector no path do ambiente de software:

Usuários bash shell:
% export PATH=$PATH:<inspector-install-dir>/bin64

Usuários csh/tcsh shell
% setenv PATH $PATH\:<inspector-install-dir>/bin64

Onde:
<inspector-install-dir> é o diretório da instalação do Intel Inspector.

Para aplicações paralelas híbridas que fazem uso da biblioteca Intel MPI Library e OpenMP recomendamos ajustar a variável de ambiente I_MPI_PIN_DOMAIN para selecionar o esquema de processos fixado para omp:

Usuários bash shell:
% export I_MPI_PIN_DOMAIN=omp

Usuários csh/tcsh shell:
% setenv I_MPI_PIN_DOMAIN omp

No esquema recomendado, o número de threads criadas em cada processo MPI será o valor definido pela variável de ambiente OMP_NUM_THREADS.

Como funciona o Intel Inspector em aplicações paralelas MPI

O Intel Inspector analisa traços gerados a partir de uma execução da aplicação. Os traços da execução são gerados pelo próprio Intel Inspector, em execuções da aplicação disparadas com auxílio a interface gráfica do Intel Inspector (inspxe-gui), ou através da sua interface linha de comando (inspxe-cl).

Aplicações que não fazem uso da biblioteca MPI podem ter os seus traços da execução gerados e analisados diretamente pela interface gráfica por meio da criação e configuração de arquivos de projetos, mas esta interface não apresenta suporte para aplicações paralelas MPI.

Em execuções de aplicações paralelas MPI, os dados coletados pela ferramenta estão correlacionados e agregados entre os processos da execução paralela. Um diretório de resultados é criado individualmente para cada processo, identificado pelo número do processo MPI (“rank”). Para cada diretório criado, o Intel Inspector faz o pós-processamento coletando e resolvendo símbolos de forma automática e gerando os traços de execução a serem analisados.

Como executar a aplicação para gerar traços da execução para detecção de condições de corrida e “deadlocks”

Para coletar e gerar traços das execuções paralelas MPI, utilize a interface de linha de comando do Intel Inspector, conforme a sintaxe descrita abaixo:

Sintaxe:
mpirun -n <procs> -host <no> inspxe-cl -r <resultado> -collect ti3 -- app.x

Onde:
<procs> é o número de processos da execução MPI;
<no> é a máquina utilizada, por exemplo, phi03;
<resultado> é um nome atribuído ao resultado da coleta realizada. O Intel Inspector irá criar e armazenar os dados coletados no diretório <resultado>.<no> ;
ti3 ajusta a coleta de dados para detecção de condição de corrida e “deadlocks”
app.x é o executável (binário) da aplicação paralela.

Considere o exemplo apresentado a seguir que gera os traços da execução de uma aplicação paralela MPI, denominada appmpi.x. O Exemplo será executado na máquina phi03.ncc.unesp.br utilizando 4 processos MPI:

Gerar os traços da execução de uma aplicação paralela MPI para detecção de condições de corrida e “deadlocks”:
% mpirun -n 4 -host phi03.ncc.unesp.br inspxe-cl -r result-4mpi -collect ti3 -- appmpi.x

A execução da linha de comando do exemplo anterior produzirá o diretório result-4mpi.phi03.ncc.unesp.br que apresenta a seguinte estrutura de subdiretórios/arquivos:

Os subdiretórios data.0, data.1, data.2, data.3 são criados automaticamente e armazenam os traços coletados sobre cada processo MPI. O sufixo numérico destes subdiretórios indica o “rank” MPI correspondente que foi detectado e capturado pela ferramenta, garantindo assim que múltiplas instâncias do comando inspxe-cl invocados no mesmo diretório em diferentes nós, não sobrescreva os dados de outros processos que trabalhem em paralelo.

Os arquivos inspxe-cl.log e inspxe-cl.txt guardam informações resumidas sobre os problemas detectados e sobre os erros internos da ferramenta.

O arquivo result-4mpi.phi03.ncc.unesp.br.inspxe armazena os dados de resultado da análise para leitura no Intel Inspector. Adicionalmente, este arquivo guarda informações sobre o ambiente de trabalho, incluindo o “start time”, o nome da máquina, o ID do produto, o “CPU count” e o tipo da análise usada na coleta dos dados. Arquivos com a extensão .inspxe podem ser abertos para análise na interface gráfica do Intel Inspector conforme exemplo da linha de comando apresentado abaixo:

Carregar o arquivo result-4mpi.phi03.ncc.unesp.br.inspxe para análise no Intel Inspector GUI:
% cd result-4mpi.phi03.ncc.unesp.br
% inspxe-gui result-4mpi.phi03.ncc.unesp.br.inspxe &

Experiência de uso do Intel Inspector na radiação do BRAMS no NCC/UNESP

A radiação RRTMG é um dos módulos que consomem maior tempo de processamento no BRAMS, ocupando cerca de 16,42% do tempo efetivo de uso da CPU, segundo dados extraídos do Intel® VTune™ Amplifier. O código da RRTMG é composto de 80 arquivos fonte escritos em linguagem Fortran 90, totalizando cerca de 172.000 linhas de código, distribuídas em 55 funções implementadas em diferentes módulos. O código da RRTMG foi produzido pelo grupo de pesquisa externo ao BRAMS e suas funções (veja Figura 1) foram acopladas no BRAMS por um driver confeccionado pelo grupo do BRAMS.

O código da RRTMG calcula a radiação para uma coluna da atmosfera. O driver contém um laço que invoca a RRTMG para cada uma das colunas da atmosfera sob sua guarda (subdomínio MPI). Buscamos paralelizar OpenMP esse laço. Para tanto, inserimos diretiva OpenMP para execução simultânea das iterações do laço. Como diversas variáveis referenciadas no interior do laço são globais ao laço, a lista de variáveis privatizadas é longa.

Embora os cálculos da radiação para colunas atmosféricas distintas sejam independentes entre si, não há garantias que a implementação da RRTMG seja “threadsafe”. De fato, o uso de variáveis globais é uma prática de programação comumente empregada dentro dos módulos do BRAMS podendo inviabilizar a aplicação do paralelismo OpenMP. Sem exceção, essas e outras estruturas de programação (variáveis SAVE, áreas “scratch” compartilhadas”) são práticas comuns em módulos da meteorologia, provavelmente presentes no módulo da RRTMG, dificultando ainda mais a percepção e a compreensão dos erros de paralelização.


Figura 1: Funções da RRTMG acopladas via driver.

Utilizamos o Intel Inspector para detectar condições de corrida ocultas no código original da radiação RRTMG após sua paralelização por OpenMP conforme sumarizado acima. O BRAMS apresenta uma codificação complexa, de difícil interpretação, até mesmo para as mais avançadas ferramentas de depuração. O Intel Inspector realiza análises dinâmicas e eficientes. Análises dinâmicas permitem testar e avaliar uma aplicação em tempo de execução tendo como principal vantagem a possibilidade de revelar erros de programação em alto grau de abstração. Analisamos alguns dos problemas que foram reportados por esta ferramenta nos módulos da RRTMG e propomos soluções para os casos confirmados. Em conjunto com a análise realizada, apresentamos a interface gráfica do Intel Inspector e as suas funcionalidades para detecção de condições de corrida. Finalizamos o documento apresentando o total de problemas analisados informando quantos destes problemas foram corretamente detectados (problemas confirmados), falsamente detectados (problemas falsos positivos) e quantos deles não foram detectados (problemas falsos negativos).

A interface gráfica

A Figura 2 apresenta a janela principal do Intel Inspector GUI (inspxe-gui), onde são exibidos os problemas detectados nos módulos da radiação RRTMG do BRAMS. Para fins didáticos, consideramos a execução paralela utilizando apenas 2 threads e dividimos a janela em quadrantes para descrever as funcionalidades da interface gráfica:


Figura 2: Janela de apresentação do Intel® Inspector XE.

O quadrante 1 apresenta uma lista com os problemas reportados pelo Intel Inspector. Estes problemas estão identificados na coluna 1 pelos identificadores (ID) P1, P2, P3, ... A coluna 2 indica o nível de severidade do problema. O nível de severidade serve para o usuário priorizar o seu trabalho em função da gravidade do problema, sendo representada pelos símbolos: (Critical: o mais sério de todos), (Error: menos sério que Critical), (Warning: menos sério que Error) e (Remark: o menos sério de todos). A coluna 3 da tabela mostra o tipo do problema detectado que pode ser Data race (condição de corrida), ou Dead lock (trava). Na coluna 4 estão apresentados os códigos fontes envolvidos no problema. A coluna 5 apresenta o módulo de execução onde o problema foi detectado. A coluna 6 descreve o estado dos problemas analisados. O estado é definido pelo próprio usuário com base na sua análise pessoal e serve para o controle no desenvolvimento do software, na troca de informações entre os membros de um grupo colaborativo de trabalho. De acordo com a análise feita pelo usuário, os problemas poderão ser classificados como: New (problema recente, ainda não analisado), Not fixed (não corrigido, requer investigação), Fixed (problema requer correção e foi corrigido com sucesso), Regression (requer mais investigação, pois o problema foi marcado como Fixed, mas ele persiste), Confirmed (problema confirmado, mas ainda não foi corrigido), Not a problem (falso positivo, não necessita de correção) e Defered (investigação adiada sobre um problema que pode, ou não ser corrigido futuramente).

O quadrante 2 filtra os problemas detectados e que são exibidos no quadrante 1. É possível filtrar os problemas pelo nível de severidade (Severity), tipo (Type), código fonte (Source), módulo (Module) e estado (State). Adicionalmente, a ferramenta ainda possibilita a filtragem por problemas que foram suprimidos (Suppressed) e por problemas que já foram investigados (Investigated) pelo usuário. Com o filtro desativado, é possível observar que a ferramenta reportou um total de 25 problemas nos módulos da radiação RRTMG, todos com nível de severidade Error e todos do tipo condição de corrida.

O quadrante 3 exibe o código fonte onde está localizado o problema que foi selecionado no quadrante 2. Neste quadrante é possível visualizar a linha, o código fonte (Source), a função/sub-rotina (Function) e o módulo (Module) onde o problema ocorreu. Também pode ser visualizado neste quadrante, a pilha de chamadas das sub-rotinas que foram invocadas até a detecção do problema reportado.

O quadrante 4 mostra a linha de execução no tempo (Timeline) que acompanha a execução dos threads no código fonte apresentado. A linha de execução aponta graficamente os conflitos no acesso a posições de memória.

Outras funcionalidades do Intel Inspector GUI serão apresentadas adiante, junto com a análise de alguns dos problemas reportados pela ferramenta.

Análise do problema P1

Ao clicar no problema P1 no quadrante 1 da janela da Figura 2, será apresentada a sua localização dentro do código fonte (veja quadrante 3 da janela da Figura 2). O label Write no campo Description indica uma operação de escrita sobre a variável hvrclc (linha 157 do código). Observe que esta operação de escrita é repetida duas vezes sobre o mesmo trecho de código, mostrando que dois threads competem pela execução desta operação. A linha de execução no tempo, apresentada no quadrante 4 da janela da Figura 1 mapeia a execução destes threads, identificados por thread #0 (thread mestre) e thread #1. Ao posicionar o ponteiro do mouse sobre cada um dos threads na linha de execução, confirmamos a operação de escrita na mesma posição de memória (veja losangos de cor laranja e branco). Efetuando um duplo clique em P1, será aberto o código expandido, conforme apresentado na Figura 3:


Figura 3: Código expandido.

A parte superior da janela da Figura 3 apresenta um trecho de código executado pelo thread #0 (lado esquerdo da janela) e a pilha de chamadas das sub-rotinas (lado direito da janela) que este thread percorreu até atingir este trecho do código. Note que a pilha de chamadas do thread #0 considera todo o contexto da execução do BRAMS, pois se trata do thread mestre (MasterThread) que segue a linha de execução do programa desde o seu início.

A parte inferior da janela da Figura 3 apresenta o código e a pilha de chamadas que foram usados pelo thread #1 (WorkerThread). A pilha do thread #1 considera somente o contexto da execução paralela OpenMP, que se inicia dentro da sub-rotina rrtm_subdriver() (veja indicação em Call Stack: rrtm_subdriver_$omp$parallel_for)

É possível navegar pela pilha de chamadas de ambos os threads e verificar o local onde as variáveis foram referenciadas, partindo do topo para a base da pilha. A Figura 4 mostra que navegando por esta pilha de chamadas, verificamos que hvrclc é uma variável global declarada no módulo rrlw_vsn, acessada via USE pelo thread #1 (veja linha 25 de rrtmg_lw_cldprmc.f90):


Figura 4: Variável global hvrclc sendo acessada por USE pelo thread #1.

O uso da variável global hvrclc e a escrita desta variável dentro da região paralela justifica o diagnóstico de condição de corrida sustentado pelo Intel Inspector. Apesar disto, hvrclc não é utilizada pelo programa, conforme mostra uma pesquisa feita por esta variável no código do BRAMS (veja Figura 5):


Figura 5: Variável global não é utilizada pelo BRAMS.

O problema P1 não é considerado crítico, pois ele não influencia na computação dos resultados do BRAMS. Por outro lado, as operações de escrita são realizadas desnecessariamente pelos threads, sem utilidade determinada. Podemos minimizar este problema criando um procedimento (sub-rotina) para inicializar o valor de hvrclc antes da região paralela, ou simplesmente eliminar a atribuição, visto que seu valor não é utilizado pela aplicação.

Análise do problema P2

A janela de apresentação do problema P2 pode ser visualizado na Figura 6:


Figura 6: Apresentação do problema P2.

O campo Sources da janela da Figura 6 indica que o problema de condição de corrida observado em P2 ocorre em mais de um código fonte. Clicando no ícone ► ao lado esquerdo de P2, obtemos um painel expandido que exibe os subproblemas relacionados a estes códigos, conforme apresentado na Figura 7:


Figura 7: Painel expandido do problema P2.

Ao clicar sobre cada item do painel expandido da janela da Figura 7, visualizamos a linha do código onde ocorre cada um dos subproblemas (veja quadrante inferior esquerdo da janela). Note que todos os subproblemas de P2 referenciam sempre as mesmas variáveis, diferenças ocorrem apenas nas sub-rotinas que utilizam estas variáveis e no nível da pilha de chamadas das sub-rotinas. Resolver um dos problemas listados no painel expandido poderá solucionar outros itens deste mesmo painel. Analisaremos o primeiro item do painel (veja Figura 8) que mostra uma possível condição de corrida entre a linha 157 do código rrtmg_lw_cldprop.f90 e a linha 432 do código rtm_driver.f90. Efetuando um duplo clique em P2, obtemos a janela apresentada na Figura 8:


Figura 8: Possível problema de condição de corrida localizado nos códigos rtm_driver.f90 e rrtmg_lw_cldprop.f90.

Na janela da Figura 8, o thread #1 escreve a variável nlay na linha 432 de rtm_driver.f90. A escrita é feita dentro da região paralela, conforme indicado pela diretiva $omp$parallel_for (veja em Call Stack).

Na Figura 9 verificamos que nlay não foi declarada privada na cláusula OpenMP que rege a região paralela e, portanto, ela será compartilhada pelos threads.


Figura 9: Cláusula OpenMP.

Consultando rrtmg_lw_cldprop.f90 na pilha de chamadas, concluímos que nlayers, lida pelo thread #0 corresponde a própria variável nlay que é escrita pelo thread #1 na linha 432 de rtm_driver.f90. A leitura e escrita da variável compartilhada nlay na região paralela justifica a condição de corrida reportada pelo Intel Inspector e o problema P2 está confirmado. A solução para este problema é evitar a escrita de nlay dentro da região paralela, fazendo nlay=mzp-1 na região sequencial antes da abertura da região paralela pela diretiva OpenMP.

Análise do problema P3

O problema P3 está apresentado na janela da Figura 10:


Figura 10: Apresentação do problema P3.

Efetuando um duplo clique em P3, será aberta a janela da Figura 11:


Figura 11: Possível problema de condição de corrida localizado nos códigos rrtmg_lw_taumol.f90 e rrtmg_lw_rad.f90.

A variável oneminus apresentada na janela da Figura 11 é uma variável global, declarada no módulo rrlw_con, escrita pelo thread #0 (linha 449 de rrtmg_lw_rad.f90) e lida pelo thread #1 (linha 516 de rrtmg_lw_taumol.f90). Pesquisando por oneminus no código concluímos que ela foi escrita várias vezes ao longo da execução dos threads, mas a escrita utiliza um valor constante, não sendo considerado um problema crítico. Não há razão para declarar privada uma variável que faz uso de um valor constante. Neste caso, a solução proposta para o problema P3 é declarar oneminus como PARAMETER, inicializando-a fora da região paralela.

Análise do problema P4

A Figura 12 apresenta o problema P4:


Figura 12: Apresentação do problema P4.

A Figura 13 apresenta mais informações sobre o problema P4:


Figura 13: Mais informações sobre o problema P4.

No problema P4, o array fluxfac é uma variável global declarada no módulo rrlw_con. A variável é escrita na região paralela pelo thread #0 (linha 451 de rrtmg_lw_rad.f90) e lida pelo thread #1, também dentro da região paralela (linha 579 de rrtmg_lw_rtrnmc.f90). Trata-se, portanto, de um problema de condição de corrida confirmado. Observe que fluxfac recebe um valor constante, mas não pode ser declarado como PARAMETER, pois o valor atribuído a esta variável é uma função. A solução para o problema P4 é criar uma sub-rotina para inicializar fluxfac, antes da região paralela.

Análise do problema P5

A Figura 14 apresenta o problema P5:


Figura 14: Apresentação do problema P5.

A Figura 15 apresenta outras informações sobre o problema P5:


Figura 15: Possível problema de condição de corrida em P5.

Na janela da Figura 15 observamos uma possível condição de corrida que ocorre quando o array semiss é escrito pelo thread #0 e este mesmo array é lido pelo thread #1. O array semiss é uma variável local, declarada dentro da sub-rotina rrtmg_lw(), conforme apresentado na Figura 16:


Figura 16: Declaração local do array semiss na sub-rotina rrtmg_lw().

Cada thread escreve em sua cópia desse array. Consequentemente, threads distintas efetuam operações em áreas distintas de memória (privadas), não constituindo um problema crítico para o BRAMS. O problema P5 reportado pelo Intel Inspector é considerado um ‘falso positivo’ e deve ser ignorado.

Análise do problema P25

A janela da Figura 17 mostra o problema P25:


Figura 17: Apresentação do problema P25.

A janela da Figura 18 apresenta outras informações sobre o problema P25:


Figura 18: Possível problema de dependência de saída em P25.

Nas janelas das Figuras 17 e 18, podemos observar que é feita uma inicialização da variável aot_rtm_lw dentro da região paralela. Os threads #0 e #1 escrevem aot_rrtm_lw na linha 1168 do código caracterizando outra condição de corrida. Analisando o uso de aot_rrtm_lw vemos que esta variável é um array global declarado no módulo mem_rrtm. O array é calculado na radiação RTM e lido pelo módulo da química tendo, portanto, utilidade dentro do programa.

A janela da Figura 19 apresenta o um trecho de código subsequente à inicialização de aot_rrtm_lw, dentro da mesma região paralela, onde esta variável é novamente acessada pelos threads:


Figura 19: Variável global aot_rrtm_lw acessada dentro da região paralela.

Conforme podemos observar na linha 1286 do código na janela da Figura 19, aot_rrtm_lw é um array tridimensional que é lido e escrito na posição (i,j,np). Uma vez que o acesso a aot_rrtm_lw na região paralela é feito em subdomínios, e estes subdomínios são independentes, a operação de leitura/escrita deste array não compromete sua execução paralela. No entanto, observe na linha 1168 que a inicialização da variável é feita no domínio global de aot_rrtm_lw resultando na escrita das posições deste array que são compartilhadas entre os threads. Portanto, o problema P25 está confirmado e necessita ser corrigido. A solução para este problema é permitir que cada thread inicialize a sua própria porção deste array correspondente ao subdomínio em que ele atua na região paralela. Por exemplo, fazendo aot_rrtm_lw(ia_thread:iz_thread, ja_thread:jz_thread, :) = 0.0, onde: ia_thread e iz_thread são os índices inferior e superior do subdomínio no eixo i e ja_thread e jz_thread são os índices inferior e superior do subdomínio no eixo j.

Problemas falsos negativos

A janela da Figura 20 apresenta os problemas detectados pelo Intel Inspector quando aumentamos o número de threads da rodada paralela de 2 para 3:


Figura 20: Problemas detectados pelo Intel Inspector com utilizando 3 threads.

Note na janela da Figura 20 que após o aumento do número de threads, o número de problemas reportados pelo Intel Inspector saltou de 25 para 62 itens. Obviamente, parte dos problemas reportados na nova rodada são equivalentes aos problemas já detectados na rodada paralela anterior com 2 threads e podem ser suprimidos com uso de arquivos de supressão (para maiores informações, consulte https://software.intel.com/en-us/node/622565). Por outro lado, alguns dos problemas reportados na rodada paralela com 3 threads são novos e merecem ser investigados. Considere, por exemplo, o problema P29 apresentado na janela da Figura 21 que aponta para uma dependência de saída na variável sig que é acessada dentro da região paralela:


Figura 21: Problema P29.

A janela da Figura 22 apresenta como cada um dos threads acessam a variável sig:


Figura 22: Acesso dos threads #1 e #0 a variável sig.

Conforme apresentado na janela da Figura 22, o array sig é uma variável global que é acessada por USE pelos threads #1 e #0 e o problema P29 está confirmado, sendo ele um problema ‘falso negativo’ da rodada paralela anterior com 2 threads.

Resumo da análise realizada

Após análise dos 62 problemas reportados pelo Intel Inspector na nova rodada paralela com 3 threads, confirmamos a existência de 55 problemas, sendo 11 deles equivalentes aos problemas já confirmados e reportados na rodada paralela anterior com 2 threads. Os outros 44 problemas confirmados são problemas novos, detectados somente na nova rodada, sendo considerados problemas ‘falsos negativos’ da rodada paralela anterior. Os 7 problemas restantes não foram confirmados e são considerados problemas ‘falsos positivos’.

Heurísticas usadas pelo Intel Inspector para eliminar problemas ‘falsos positivos’ podem esconder problemas reais, levando à obtenção dos problemas ‘falsos negativos’. Consequentemente, é central utilizar a ferramenta diversas vezes, aumentando o número de threads, para detectar problemas não detectados nas execuções com menor número de threads.

Em suma, o Intel Inspector é uma ferramenta extremamente útil para a tarefa crucial de paralelizar um programa longo utilizando OpenMP. Como demonstramos, ela acelera o trabalho.

Referências

O Código Open-Source utilizado neste artigo está disponível para consulta em http://brams.cptec.inpe.br

Para obtener información más completa sobre las optimizaciones del compilador, consulte nuestro Aviso de optimización.