Interface de passagem de mensagens
MPI
| |
---|---|
Dados básicos
| |
desenvolvedor | Fórum MPI |
Versão atual |
Versão 4.0 (PDF; 4,3 MB) (9 de junho de 2021) |
sistema operacional | Linux , Unix , Microsoft Windows NT , macOS |
categoria | API |
Falando alemão | Não |
Site da MPI |
Message Passing Interface ( MPI ) é um padrão que descreve a troca de mensagens durante cálculos paralelos em sistemas de computador distribuídos. Ele define uma coleção de operações e suas semânticas, ou seja, uma interface de programação , mas nenhum protocolo específico e nenhuma implementação.
Um aplicativo MPI geralmente consiste em vários processos que se comunicam entre si , todos iniciados em paralelo no início da execução do programa. Todos esses processos trabalham juntos em um problema e usam mensagens para troca de dados que são explicitamente enviadas de um processo para outro. Uma vantagem desse princípio é que a troca de mensagens também funciona além dos limites do computador. Os programas MPI paralelos podem, portanto, ser executados em clusters de PC (aqui, a troca de mensagens, por exemplo, via TCP ) e em computadores paralelos dedicados (aqui a troca de mensagens ocorre por meio de uma rede de alta velocidade, como InfiniBand ou Myrinet ou via rede principal compartilhada memória ).
história
Em 1992, o desenvolvimento do padrão MPI 1.0 começou com rascunhos (novembro de 1992, fevereiro de 1993, novembro de 1993). O ponto de partida foram as bibliotecas de comunicação mais antigas, como PVM, PARMACS, P4, Chameleon e Zipcode. O padrão apareceu em 5 de maio de 1994 com
- Comunicação ponto a ponto
- comunicação global
- Grupos, contexto e comunicadores
- Arredores
- Interface de criação de perfil
- Integração de linguagem para C e Fortran 77
Em junho de 1995, os erros foram corrigidos com o MPI 1.1.
Em 18 de julho de 1997, foi publicada a versão estável MPI 1.2, que, além de novas correções de erros, permite a identificação da versão. Também é conhecido como MPI-1.
Em 30 de maio de 2008, o MPI 1.3 foi lançado com mais correções de bugs e esclarecimentos.
Ao mesmo tempo que a versão 1.2, o padrão MPI 2.0 foi adotado em 18 de julho de 1997. Isso também é conhecido como MPI-2 e inclui as seguintes extensões:
- entrada / saída de arquivo paralelo
- gerenciamento de processo dinâmico
- Acesso à memória de outros processos
- integração de linguagem adicional de C ++ e Fortran 90
Em 23 de junho de 2008, as partes anteriormente separadas MPI-1 e MPI-2 foram combinadas em um documento comum e publicadas como MPI 2.1. MPI Standard Versão 2.2 é datado de 4 de setembro de 2009 e contém outras melhorias e pequenas melhorias.
Em 21 de setembro de 2012, o MPI Forum publicou MPI-3, que incorpora novas funcionalidades, como coletivos não bloqueadores, um modelo de comunicação unilateral aprimorado (RMA, Remote Memory Access), uma nova interface Fortran, comunicação relacionada à topografia e não -bloqueio de entrada e saída paralelas.
MPI-4 foi lançado em 9 de junho de 2021. As principais inovações são interfaces de funções que suportam parâmetros com uma faixa maior de valores. Anteriormente, devido ao tipo de dados de 32 bits especificado , parâmetros essenciais como B. o número de elementos de dados a serem comunicados é limitado a pouco mais de dois bilhões. Os coletivos persistentes abrem possibilidades para otimizar a comunicação repetida e, ao contrário das operações coletivas já existentes, podem ser executados em qualquer ordem. Além disso, o tratamento de erros foi aprimorado em muitos pontos e um novo modelo de sessão para o uso dinâmico dos recursos gerenciados pelo MPI foi introduzido.
Um dos principais desenvolvedores é Bill Gropp .
Comunicação ponto a ponto
O tipo mais básico de comunicação ocorre entre dois processos: um processo de envio transfere informações para um processo de recebimento. Em MPI, esta informação é embalado em chamadas mensagens com os parâmetros buffer
, count
e datatype
são descritos abaixo. Deve existir uma operação de recebimento adequada para cada operação de envio. Como a mera sequência de operações de processamento nem sempre é suficiente em aplicativos paralelos, o MPI também oferece o tag
parâmetro - somente se esse valor for idêntico para as operações de envio e recebimento, os dois se encaixarão.
Bloqueio de envio e recebimento
As operações mais simples para comunicação ponto a ponto são enviar e receber :
int MPI_Send (void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
- buf : Ponteiro para o buffer de envio
- contagem : número de elementos no buffer de envio
- tipo de dados : tipo de dados dos elementos no buffer de envio
- dest : Classificação do processo alvo
- tag : marca a mensagem
- comm : comunicador do grupo de processo
int MPI_Recv (void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status* status)
-
buf
: Ponteiro para um buffer de recebimento de tamanho suficiente -
count
: Número de elementos no buffer de recepção -
datatype
: Tipo de dados dos elementos no buffer de recebimento -
source
: Classificação do processo de origem (comsource=MPI_ANY_SOURCE
é recebido por qualquer processo) -
tag
: marcação esperada da mensagem (tag=MPI_ANY_TAG
cada mensagem é recebida com) -
comm
: Comunicador do grupo de processo -
status
: Ponteiro para uma estrutura de status na qual as informações sobre a mensagem recebida devem ser armazenadas
As duas operações são de bloqueio e assíncronas . Que significa:
-
MPI_Recv
pode ser executado antes que o associado tenhaMPI_Send
iniciado -
MPI_Recv
bloqueado até que a mensagem tenha sido completamente recebida
O seguinte se aplica de forma análoga:
-
MPI_Send
pode ser executado antes que o associado tenhaMPI_Recv
iniciado -
MPI_Send
bloqueado até que o buffer de envio possa ser reutilizado (ou seja, a mensagem foi completamente transmitida ou temporariamente armazenada em buffer)
Programa de amostra
O uso de MPI_Send
e MPI_Recv
é ilustrado no seguinte exemplo ANSI-C para 2 processos MPI:
#include "mpi.h"
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int myrank, message_size=50, tag=42;
char message[message_size];
MPI_Status status;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
if (myrank == 0) {
MPI_Recv(message, message_size, MPI_CHAR, 1, tag, MPI_COMM_WORLD, &status);
printf("received \"%s\"\n", message);
}
else {
strcpy(message, "Hello, there");
MPI_Send(message, strlen(message)+1, MPI_CHAR, 0, tag, MPI_COMM_WORLD);
}
MPI_Finalize();
return 0;
}
Comunicação sem bloqueio
A eficiência de uma aplicação paralela pode freqüentemente ser aumentada pela sobreposição da comunicação com cálculo e / ou evitando tempos de espera relacionados à sincronização. Para tanto, o padrão MPI define a chamada comunicação sem bloqueio, na qual apenas é iniciada a operação de comunicação. Uma função separada deve então ser chamada para encerrar tal operação. Ao contrário da variante de bloqueio, quando a operação é iniciada, Request
é criado um objeto que pode ser usado para verificar ou aguardar a conclusão desta operação.
int MPI_Isend (void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm, MPI_Request* request)
- ...
-
request
: Endereço da estrutura de dados que contém informações sobre a operação
int MPI_Irecv (void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request* request)
- ...
Progresso da consulta
Para saber o andamento de uma dessas operações, a seguinte operação é usada:
int MPI_Test (MPI_Request* request, int* flag, MPI_Status* status)
Onde flag=1
ou é 0
definido dependendo se a operação foi concluída ou ainda está em andamento.
Aguardando bloqueio
A seguinte operação é usada para esperar por uma MPI_Isend
- ou - MPI_Irecv
operação de forma bloqueadora :
int MPI_Wait (MPI_Request* request, MPI_Status* status)
Sincronizando o envio
As variantes síncronas MPI_Ssend
e também são MPI_Issend
definidas para as operações de envio . Nesse modo, o envio não terminará até que a operação de recebimento associada seja iniciada.
Variantes de buffer
...
Grupos e comunicadores
Os processos podem ser resumidos em grupos , em que cada processo recebe um número único, a chamada classificação . É necessário um comunicador para acessar um grupo . Se uma operação de comunicação global deve ser restrita a um grupo, o comunicador pertencente ao grupo deve ser especificado. O comunicador para o conjunto de todos os processos é chamado MPI_COMM_WORLD
.
O comm
grupo pertencente ao comunicador está incluído
int MPI_Comm_group (MPI_Comm comm, MPI_Group* group)
As operações de conjunto usuais estão disponíveis para grupos de processos.
União
Dois grupos group1
e group2
podem ser combinados em um novo grupo new_group
:
int MPI_Group_union (MPI_Group group1, MPI_Group group2, MPI_Group* new_group)
Os processos group1
mantêm sua numeração original. Os group2
que ainda não estão incluídos no primeiro são numerados consecutivamente.
Interseção
A intersecção de dois grupos é obtida com
int MPI_Group_intersection (MPI_Group group1, MPI_Group group2, MPI_Group* new_group)
diferença
A diferença entre dois grupos é obtida com
int MPI_Group_difference (MPI_Group group1, MPI_Group group2, MPI_Group* new_group)
Comunicação global
Em aplicações paralelas, muitas vezes encontramos padrões de comunicação especiais nos quais vários ou até mesmo todos os processos MPI estão envolvidos ao mesmo tempo. O padrão MPI, portanto, definiu suas próprias operações para os padrões mais importantes. Estes são aproximadamente divididos em três tipos: sincronização (barreira), comunicação (por exemplo, transmissão, coleta, tudo para todos) e comunicação acoplada com cálculo (por exemplo, redução ou varredura). Algumas dessas operações usam um processo MPI selecionado que tem uma função especial e é normalmente root
referido como. Além das operações de comunicação regulares, também existem variantes baseadas em vetores (por exemplo, Scatterv) que permitem diferentes argumentos para cada processo onde faz sentido.
Transmissão
Com a operação de transmissão , um processo MPI selecionado envia os mesmos dados para root
todos os outros processos em seu grupo comm
. A função definida para isso é idêntica para todos os processos envolvidos:
int MPI_Bcast (void *buffer, int count, MPI_Datatype type, int root, MPI_Comm comm)
O processo MPI root
disponibiliza buffer
seus dados, enquanto os outros processos transferem o endereço de seu buffer de recebimento aqui. Os parâmetros restantes devem ser iguais (ou equivalentes) em todos os processos. Depois que a função retorna, todos os buffers contêm os dados que estavam originalmente root
disponíveis apenas para.
Juntar
Com a operação Gather , o processo MPI coleta root
os dados de todos os processos envolvidos. Os dados de todos os buffers de envio são armazenados um após o outro (classificados de acordo com a classificação) no buffer de recebimento:
int MPI_Gather (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
Variante baseada em vetor
A variante baseada em vetor da operação de coleta permite um número de elementos dependente do processo:
int MPI_Gatherv (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int *recvcounts, int *displs, MPI_Datatype recvtype, int root, MPI_Comm comm)
-
recvcounts
: Campo contendo o número de elementos recebidos pelos processos individuais (apenas pararoot
relevantes) -
displs
: Campo cuja entrada i especifica a mudança no buffer de recebimento em que os dados do processo i devem ser armazenados (também apenasroot
relevante)
Deve-se observar que, nos campos, são permitidos intervalos no buffer de recebimento, mas não sobreposições. Se, por exemplo, 1, 2 e 3 elementos do tipo inteiro devem ser recebidos de 3 processos , então recvcounts = {1, 2, 3}
e deve ser displs = {0, 1 * sizeof(int), 3 * sizeof(int)}
definido.
Scatter
Com uma operação de dispersão , o processo MPI envia root
cada processo envolvido em um elemento de dados diferente, mas igualmente grande:
int MPI_Scatter (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
Variante baseada em vetor
int MPI_Scatterv (void *sendbuf, int *sendcounts, int *displs, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, int root, MPI_Comm comm)
acumulação
A acumulação é uma forma especial de operação de coleta . Os dados de todos os processos envolvidos também são coletados aqui, mas também são reduzidos a uma data usando uma operação de redução especificada. Por exemplo, deixar que o valor no processo de classificação , em seguida, proporciona Reduzir (+), o total de todos os valores: .
int MPI_Reduce (void *sendbuf, void *recvbuf, int count, MPI_Datatype type, MPI_Op op, int root, MPI_Comm comm)
As op
seguintes operações de redução predefinidas existem para o parâmetro :
Operações lógicas
-
MPI_LAND
: link AND lógico -
MPI_BAND
: operação AND bit a bit -
MPI_LOR
: link OR lógico -
MPI_BOR
: operação OR bit a bit -
MPI_LXOR
: link lógico OU exclusivo -
MPI_BXOR
: operação OR exclusiva bit a bit
Operaçoes aritimeticas
-
MPI_MAX
: Máximo -
MPI_MIN
: Mínimo -
MPI_SUM
: Total -
MPI_PROD
: Produtos -
MPI_MINLOC
: Mínimo com processo -
MPI_MAXLOC
: Máximo com processo
As operações MPI_MINLOC
e também MPI_MAXLOC
retornam a classificação do processo MPI que determinou o resultado.
Operações personalizadas
Além das operações de redução predefinidas, você também pode usar suas próprias operações de redução. Para tanto, é anunciada ao MPI uma operação de lógica binária livremente programável, que deve ser associativa e opcionalmente comutativa:
int MPI_Op_create (MPI_User_function *function, int commute, MPI_Op *op)
A função de usuário associada calcula um valor de saída de dois valores de entrada e faz isso - por razões de otimização - não apenas uma vez com escalares, mas elemento por elemento em vetores de qualquer comprimento:
typedef void MPI_User_function (void *invec, void *inoutvec, int *len, MPI_Datatype *datatype)
Redução de Prefixo
Além da acumulação mencionada acima, existe também uma variante Allreduce - que disponibiliza o mesmo resultado para todos os processos MPI e não apenas para um root
processo. A chamada redução de prefixo agora estende essa opção, não calculando o mesmo resultado para todos os processos, mas sim calculando um resultado parcial específico do processo. Por exemplo, deixar de novo o valor no processo de classificação , em seguida, proporciona leitura (+) a soma parcial dos valores de classificação para : .
int MPI_Scan (void *sendbuf, void *recvbuf, int count, MPI_Datatype type, MPI_Op op, MPI_Comm comm)
Se o próprio valor não for incluído no cálculo (ou seja, excluído), isso pode ser feito com a função de varredura exclusiva MPI_Exscan
.
Allgather
Na operação Allgather , cada processo envia os mesmos dados para todos os outros processos. É, portanto, uma operação multi-broadcast na qual não existe um processo MPI separado.
int MPI_Allgather (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm)
Todos para todos (troca total)
Com a comunicação geral - semelhante à comunicação Allgather - os dados são trocados entre todos os processos. No entanto, apenas a i- ésima parte do buffer de envio é enviada para o i- ésimo processo. Os dados que vêm do processo com classificação j são correspondentemente armazenados na j -ésima posição no buffer de recepção.
int MPI_Alltoall (void *sendbuf, int sendcount, MPI_Datatype sendtype, void *recvbuf, int recvcount, MPI_Datatype recvtype, MPI_Comm comm)
Também existe a operação de sincronização MPI_Barrier . Esta função só retorna depois que todos os processos MPI no grupo especificado alcançaram esta parte do programa.
MPI-2
Uma segunda versão do padrão MPI está disponível desde 1997, adicionando algumas extensões ao padrão MPI-1.1 ainda existente. Essas extensões incluem, entre outros
- gerenciamento de processo dinâmico, d. H. Os processos agora podem ser criados e excluídos em tempo de execução
- acesso [paralelo] ao sistema de arquivos
- comunicação unilateral
- Especificação de interfaces de linguagem adicionais (C ++, Fortran 90), em que as interfaces de linguagem para C ++ foram marcadas como obsoletas desde MPI 2.2
Exemplo: Lendo uma matriz nx (n + 1) com entrada de arquivo paralela e processos de tamanho com a classificação de números = 0… tamanho-1. A coluna n + 1 contém o lado direito do sistema de equações A * x = b na forma da matriz estendida [A, b]. As linhas da matriz são distribuídas uniformemente pelos processadores. A distribuição ocorre ciclicamente (cada processador em uma linha, após a classificação das linhas de tamanho = 0 ser servida novamente) e não em blocos (cada processador obtém um bloco contíguo de linhas n / tamanho):
ndims = 1; /* dimensions */ aosi [0] = size * (n+1); /* array of sizes */ aoss [0] = n+1; /* array of subsizes */ aost [0] = rank * (n+1); /* array of starts */ order = MPI_ORDER_C; /* row or column order */ MPI_Type_create_subarray (ndims, aosi, aoss, aost, order, MPI_DOUBLE, &ft); MPI_Type_commit (&ft);
MPI_File_open (MPI_COMM_WORLD, fn, MPI_MODE_RDONLY, MPI_INFO_NULL, &fh); MPI_File_set_view (fh, sizeof (int), MPI_DOUBLE, ft, „native“, MPI_INFO_NULL);
for (i = rank; i < n; i+=size) { MPI_File_read (fh, rdbuffer, n+1, MPI_DOUBLE, &status); for (j = 0; j < n+1; j++) { A [i / size] [j] = rdbuffer [j]; /* nur die dem Prozess zugeordneten Zeilen */ } }
MPI_File_close (&fh);
A interface segue o padrão POSIX 1003.1 com pequenas alterações devido ao paralelismo. O arquivo é aberto para leitura comum com MPI_File_open. As aberturas (visualizações) para os processos individuais são definidas com MPI_File_set_view. A variável ft (tipo de arquivo) definida anteriormente é necessária aqui, na qual uma linha com n + 1 dobra é selecionada em um bloco de tamanho * (n + 1) dobra, começando no rank de posição * (n + 1). Assim, de todo o bloco, exatamente uma linha é atribuída sucessivamente a cada processo. Este tipo é definido com MPI_Type_create_subarray e tornado conhecido no sistema MPI com MPI_Type_commit. Cada processo lê "suas" linhas com os números i = classificação, classificação + tamanho, classificação + 2 * tamanho, ... até que toda a matriz tenha sido lida com MPI_File_read. O argumento tamanho do (int) leva em consideração o tamanho da matriz, que é armazenado como um int no início do arquivo.
Benefício: Em processadores de tamanho, uma matriz pode ser armazenada de forma distribuída que não teria mais nenhum espaço na memória de um único processador. Isso também justifica a convenção de especificar a soma da memória dos núcleos individuais e nós individuais como a memória de um sistema paralelo.
Formato de arquivo:
n Zeile 0 (n+1) Zahlen für Prozess rank 0 Zeile 1 (n+1) Zahlen für Prozess rank 1 … Zeile r (n+1) Zahlen für Prozess rank r … Zeile size-1 (n+1) Zahlen für Prozess rank size-1
Zeile size (n+1) Zahlen für Prozess rank 0 Zeile size+1 (n+1) Zahlen für Prozess rank 1 … Zeile size+r (n+1) Zahlen für Prozess rank r … Zeile 2*size-1 (n+1) Zahlen für Prozess rank size-1
Zeile 2*size (n+1) Zahlen für Prozess rank 0 … … es folgen entsprechend der Zeilenzahl der Matrix ausreichend viele solcher Blöcke
A leitura real é feita com MPI_File_read. Cada processo lê sequencialmente apenas as linhas atribuídas a ele. A operação coletiva é que a biblioteca MPI pode otimizar e paralelizar a leitura. Terminada a leitura, o arquivo deve ser fechado normalmente. Isso é feito com MPI_File_close. MPI tem seus próprios tipos de dados MPI_Datatype ft e MPI_File fh para as operações. O tipo de arquivo é descrito com variáveis C normais: int ndims; int aosi [1]; int aoss [1]; int aost [1]; ordem interna;
Mais em.
Implementações
C ++, C e Fortran
A primeira implementação do padrão MPI-1.x foi MPICH do Argonne National Laboratory e da Mississippi State University . MPICH2, que implementa o padrão MPI-2.1, agora está disponível. O LAM / MPI do Ohio Supercomputing Center foi outra versão gratuita, cujo desenvolvimento foi interrompido em favor do Open MPI.
A partir da versão 1.35 das Bibliotecas Boost existe Boost.MPI, uma interface amigável C ++ para várias implementações MPI. Outros projetos, como B. TPO ++ , oferecem esta possibilidade e são capazes de enviar e receber contêineres STL.
C #
- MPI.NET (MPI1)
Pitão
- MPI para Python (MPI-1 / MPI-2)
- pyMPI
- Boost: MPI Python Bindings (desenvolvimento descontinuado)
Java
- MPJ Express
- mpiJava (MPI-1; desenvolvimento descontinuado)
Pérola
R.
Haskell
Veja também
literatura
- Heiko Bauke, Stephan Mertens: Cluster Computing. Springer, 2006, ISBN 3-540-42299-4
- William Gropp, Ewing Lusk, Anthony Skjellum: MPI - An Introduction - Portable Parallel Programming with the Message-Passing Interface . Munich 2007, ISBN 978-3-486-58068-6 .
- M. Firuziaan, O. Nommensen: Parallel Processing via MPI & OpenMP . Linux Enterprise, 10/2002
- Marc Snir , Steve Otto, Steven Huss-Lederman, David Walker, Jack Dongarra : MPI - A referência completa , Vol 1: O núcleo MPI. 2ª Edição. MIT Press, 1998
- William Gropp, Steven Huss-Lederman, Andrew Lumsdaine, Ewing Lusk, Bill Nitzberg, William Saphir, Marc Snir: MPI-The Complete Reference , Vol. 2: The MPI-2 Extensions. The MIT Press, 1998.
Links da web
- MPI
- Fórum MPI
- EuroMPI - MPI Users 'Group Meeting (edição 2010)
- Open MPI - implementação de MPI disponível gratuitamente
- MPICH - implementação de MPI disponível gratuitamente
- MPICH2 - implementação MPI disponível gratuitamente
- MVAPICH - Implementação MPI com VAPI
- Impulso
- TPO ++
Evidência individual
- ↑ MPI Documentos ( Memento do originais de 6 de Novembro de 2006, no Internet Archive ) Info: O arquivo de ligação foi inserido automaticamente e ainda não foi marcada. Verifique o link original e o arquivo de acordo com as instruções e, em seguida, remova este aviso.
- ^ O futuro de MPI . (PDF)
- ↑ Mudanças no MPI-4.0
- ↑ MPI-2: Extensões para a interface de passagem de mensagens ( Memento do originais de 21 de Setembro de 2007, na Internet Archive ) Info: O arquivo de ligação foi inserido automaticamente e ainda não foi marcada. Verifique o link original e o arquivo de acordo com as instruções e, em seguida, remova este aviso.