Memória Distribuída
Nesta seção:
A memória distribuída é um modelo de paralelismo em que múltiplos processos executam em diferentes nós (servidores), comunicando-se via troca de mensagens.
Conceitos Básicos
Processos: Unidades de execução independentes, cada uma com sua própria memória
MPI (Message Passing Interface): Padrão para comunicação entre processos
Troca de mensagens: Processos se comunicam explicitamente enviando/recebendo dados
Escalabilidade: Pode utilizar centenas ou milhares de nós
Características:
Processos podem estar em diferentes nós
Cada processo tem seu próprio espaço de memória
Comunicação é explícita (mais complexa)
Escalabilidade quase ilimitada
MPI
MPI é o padrão mais utilizado para programação com memória distribuída. No GridUnesp, temos duas implementações:
Intel MPI (recomendado, otimizado para hardware Intel)
OpenMPI (open-source, amplamente utilizado)
Exemplo em C
#include <mpi.h>
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
int numtasks, rank, len, rc;
char hostname[MPI_MAX_PROCESSOR_NAME];
// Inicializa o MPI
MPI_Init(&argc, &argv);
// Número total de processos
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
// Rank (ID) deste processo
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
// Nome do nó onde está executando
MPI_Get_processor_name(hostname, &len);
printf("Processo %d de %d executando em %s\n",
rank, numtasks, hostname);
// Finaliza o MPI
MPI_Finalize();
return 0;
}
Compilando
# Com Intel MPI
module load intel/compilers
module load intel/mpi
mpiicc mpi_hello.c -o mpi_hello
# Com OpenMPI
module load openmpi/4.1.5
mpicc mpi_hello.c -o mpi_hello
Script de Submissão
No SLURM, o número de processos é controlado pela diretiva -n (--ntasks).
#!/bin/bash
#SBATCH -J mpi_test
#SBATCH -N 4 # 4 nós
#SBATCH -n 112 # 112 processos totais
#SBATCH --ntasks-per-node=28 # 28 processos por nó
#SBATCH -t 02:00:00
#SBATCH --mem-per-cpu=4G
export INPUT="dados_entrada/ mpi_hello"
export OUTPUT="resultados_mpi/"
# Carrega o módulo MPI
module load openmpi/4.1.5
# Executa com srun (recomendado para SLURM)
job-nanny mpirun -n 112 ./mpi_hello
Distribuição de Processos
Opção 1: Especificar número total de processos
#SBATCH -n 112 # 112 processos totais
# O SLURM decide como distribuir entre os nós
Opção 2: Especificar processos por nó
#SBATCH -N 4
#SBATCH --ntasks-per-node=28 # 28 processos em cada nó
# Total: 4 × 28 = 112 processos
Opção 3: Especificar mínimo e máximo de nós
#SBATCH -n 112
#SBATCH -N 2-4 # Mínimo 2, máximo 4 nós
# SLURM decide a distribuição ótima
Comunicação entre Processos
Exemplo: Soma Distribuída
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#define ARRAY_SIZE 1000000
int main(int argc, char *argv[]) {
int rank, size;
int *data = NULL;
int local_sum = 0, global_sum = 0;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
// Processo 0 inicializa os dados
if (rank == 0) {
data = (int*)malloc(ARRAY_SIZE * sizeof(int));
for (int i = 0; i < ARRAY_SIZE; i++)
data[i] = i + 1;
}
// Calcula quantos elementos cada processo recebe
int chunk_size = ARRAY_SIZE / size;
int *local_data = (int*)malloc(chunk_size * sizeof(int));
// Distribui os dados (MPI_Scatter)
MPI_Scatter(data, chunk_size, MPI_INT,
local_data, chunk_size, MPI_INT,
0, MPI_COMM_WORLD);
// Cada processo calcula sua soma local
for (int i = 0; i < chunk_size; i++)
local_sum += local_data[i];
// Reúne as somas parciais (MPI_Reduce)
MPI_Reduce(&local_sum, &global_sum, 1, MPI_INT,
MPI_SUM, 0, MPI_COMM_WORLD);
if (rank == 0) {
printf("Soma total: %d\n", global_sum);
free(data);
}
free(local_data);
MPI_Finalize();
return 0;
}
Híbrido: MPI + OpenMP
É possível combinar os dois modelos:
MPI para comunicação entre nós
OpenMP para paralelismo dentro de cada nó
#!/bin/bash
#SBATCH -J hibrido
#SBATCH -N 4 # 4 nós
#SBATCH --ntasks-per-node=4 # 4 processos MPI por nó
#SBATCH -c 7 # 7 threads por processo MPI
#SBATCH -t 24:00:00
#SBATCH --mem=64G
export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
export INPUT="dados.dat programa_hibrido"
export OUTPUT="resultados/"
module load openmpi/4.1.5
job-nanny srun ./programa_hibrido
Total de threads: 4 nós × 4 processos × 7 threads = 112 threads
Quando Usar Memória Distribuída
✅ Ideal para:
Problemas que não cabem em um único nó
Aplicações que escalam bem com mais nós
Simulações que requerem muitos recursos
Quando a memória total necessária > 128 GB
❌ Não recomendado para:
Problemas pequenos que cabem em um nó
Aplicações com muita comunicação (overhead)
Quando a latência da rede é crítica
Escalabilidade
Lei de Amdahl
A aceleração máxima é limitada pela porção serial do código:
Speedup = 1 / (S + (1-S)/P)
Em que:
S = fração serial
P = número de processos
Exemplo: Se 10% do código é serial, o speedup máximo teórico é 10x, mesmo com infinitos processos.
Testando Escalabilidade
#!/bin/bash
# Teste de escalabilidade forte (problema fixo)
for nodes in 1 2 4 8; do
cat > job_${nodes}n.sh << EOF
#!/bin/bash
#SBATCH -J mpi_${nodes}n
#SBATCH -N ${nodes}
#SBATCH --ntasks-per-node=28
#SBATCH -t 01:00:00
#SBATCH --mem-per-cpu=2G
module load openmpi/4.1.5
srun ./programa_mpi
EOF
sbatch job_${nodes}n.sh
done
Problemas Comuns
Deadlock: Processos esperando uns pelos outros indefinidamente
// ERRADO: pode causar deadlock MPI_Send(...); // Envio bloqueante MPI_Recv(...); // Recebimento // CORRETO: usar send/receive combinados MPI_Sendrecv(...);
Race condition: Acesso concorrente sem sincronização
// Use operações atômicas ou coletivas MPI_Reduce(&local, &global, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD);
Granularidade muito fina: Overhead de comunicação domina
Agrupe mensagens pequenas em mensagens maiores
Use comunicação coletiva quando possível
Dicas de Otimização
Comunicação coletiva vs ponto-a-ponto
Use
MPI_Reduce,MPI_Bcast,MPI_Scatterquando apropriadoSão otimizadas para o hardware
Sobreposição comunicação/computação
MPI_Isend(...); // Não bloqueante // Computação enquanto a mensagem é enviada MPI_Wait(...); // Espera conclusão
Use comunicadores para organizar grupos
MPI_Comm_split(MPI_COMM_WORLD, color, key, &new_comm);
Evite mensagens muito pequenas
Overhead de latência domina
Agrupe dados em buffers maiores
Ver também
MPI - Message Passing Interface
Memória Compartilhada - Para paralelismo dentro do nó
Processando Simulações - Conceitos básicos de submissão
Otimizando o Desempenho - Como encontrar a configuração ideal
Guia Completo de Armazenamento - /store/ para jobs multi-nó