Estou trabalhando em um problema que pode ser paralelo usando uma única operação mpi_allgather ou uma operação mpi_scatter e uma mpi_gather. Essas operações são chamadas dentro de um loop while, portanto, podem ser chamadas várias vezes.
Na implementação com um esquema MPI_allgather, estou reunindo um vetor distribuído em todos os processos para solução de matriz duplicada. Na outra implementação, reuno o vetor distribuído em um único processador (o nó raiz), resolvo o sistema linear nesse processador e depois disperso o vetor da solução em todos os processos.
Estou curioso para saber se o custo de uma operação geral é significativamente maior do que as operações de dispersão e coleta combinadas. O comprimento da mensagem desempenha um papel significativo em sua complexidade? Isso varia entre implementações de mpi?
Editar:
fonte
MPI_Scatter
seguido porMPI_Gather
não fornece a mesma comunicação semântica queMPI_Allgather
. Talvez exista redundância quando você expressa a operação de qualquer maneira?MPI_Gather
seguido de aMPI_Bcast
?Respostas:
Primeiro, a resposta exata depende de: (1) uso, isto é, argumentos de entrada de função, (2) qualidade e detalhes da implementação do MPI e (3) o hardware que você está usando. Freqüentemente, (2) e (3) estão relacionados, como quando o fornecedor de hardware otimiza o MPI para sua rede.
Em geral, a fusão de coletivos MPI é melhor para mensagens menores, pois os custos de inicialização podem não ser triviais e a sincronização causada pelo bloqueio de coletivos deve ser minimizada se houver variação no tempo de computação entre as chamadas. Para mensagens maiores, o objetivo deve ser minimizar a quantidade de dados enviados.
Por exemplo, em teoria,
MPI_Reduce_scatter_block
deve ser melhor do que oMPI_Reduce
seguidoMPI_Scatter
, embora o primeiro seja frequentemente implementado em termos do último, de modo que não exista vantagem real. Existe uma correlação entre a qualidade da implementação e a frequência de uso na maioria das implementações do MPI, e os fornecedores obviamente otimizam as funções para as quais isso é exigido pelo contrato da máquina.Por outro lado, se alguém está em um Blue Gene,
MPI_Reduce_scatter_block
usar usandoMPI_Allreduce
, que faz mais comunicação do queMPI_Reduce
eMPI_Scatter
combinado, é na verdade um pouco mais rápido. Isso é algo que eu descobri recentemente e é uma violação interessante do princípio da consistência do desempenho no MPI (esse princípio é descrito em mais detalhes em "Diretrizes de desempenho do MPI autoconsistentes " ).No caso específico de dispersão + coletar versus reunir, considere que no primeiro, todos os dados devem ir para e de um único processo, o que o torna um gargalo, enquanto no geral, os dados podem fluir para dentro e para fora de todas as classificações imediatamente , porque todas as classificações têm alguns dados para enviar a todas as outras classificações. No entanto, o envio de dados de todos os nós de uma só vez não é necessariamente uma boa ideia em algumas redes.
Por fim, a melhor maneira de responder a essa pergunta é fazer o seguinte em seu código e responder a pergunta por experiência.
Uma opção ainda melhor é fazer com que seu código o avalie experimentalmente durante as duas primeiras iterações e use o que for mais rápido nas demais iterações:
fonte
Jeff está absolutamente certo sobre a única maneira de ter certeza é medir - afinal somos cientistas, e esta é uma pergunta empírica - e oferece excelentes conselhos sobre como implementar essas medições. Permitam-me agora oferecer uma visão contrária (ou, talvez, complementar).
Há uma distinção a ser feita entre escrever um código para ser amplamente usado e ajustá-lo para um fim específico. Em geral, estamos fazendo o primeiro - construindo nosso código para que: a) possamos usá-lo em uma ampla variedade de plataformas eb) o código seja sustentável e extensível nos próximos anos. Mas, às vezes, estamos fazendo o outro - temos um ano de alocação em uma grande máquina e estamos aumentando o conjunto necessário de grandes simulações e precisamos de uma certa linha de base de desempenho para obter o que precisamos durante a hora da alocação concedida.
Quando estamos escrevendo código, torná-lo amplamente utilizável e sustentável é muito mais importante do que reduzir alguns por cento do tempo de execução em uma máquina específica. Nesse caso, a coisa certa a fazer é quase sempre usar a rotina que melhor descreve o que você deseja fazer - essa geralmente é a chamada mais específica que você pode fazer e fazer o que deseja. Por exemplo, se um allgather straight ou allgatherv faz o que você deseja, você deve usá-lo em vez de executar suas próprias operações dispersas / combinadas. As razões são as seguintes:
Nesse caso bastante comum, se você descobrir que algum coletivo MPI funciona de maneira excessivamente lenta em sua máquina, a melhor coisa a fazer é registrar um relatório de bug com o fornecedor de MPI; você não deseja complicar seu próprio software tentando solucionar o código do aplicativo, o que deve ser corrigido corretamente no nível da biblioteca MPI.
No entanto . Se você estiver no modo "tuning" - você tem um código de trabalho, precisa escalar escalas muito grandes em um curto período de tempo (por exemplo, uma alocação de um ano) e criar um perfil de seu código e descobrimos que essa parte específica do seu código é um gargalo, faz sentido começar a executar essas afinações muito específicas. Espero que eles não sejam partes de longo prazo do seu código - idealmente, essas alterações permanecerão em algum ramo específico do projeto do seu repositório - mas você pode precisar fazer isso. Nesse caso, a codificação de duas abordagens diferentes, diferenciadas pelas diretivas de pré-processador, ou uma abordagem de "autotuning" para um padrão de comunicação específico - pode fazer muito sentido.
Portanto, não estou discordando de Jeff, só quero adicionar um contexto sobre quando você deve se preocupar o suficiente com essas questões de desempenho relativo para modificar seu código e lidar com isso.
fonte