Sumário:
Introdução
Charm++ é uma linguagem paralela de troca de mensagem e um
sistemas
runtime. Foi
implementado como um conjunto de bibliotecas para C++, é
eficiente e portável para uma grande variedade de
máquinas. Vem com o código fonte e é livre para
uso não comercial.
Foi desenvolvida no Department of Computer Science na University of
Illinois at Urbana-Champaign.
Foi projetada para ser executada em diferentes arquiteturas MIMD sem
necessidade de modificação do código-fonte. O
Charm++ suporta herança múltipla, ligação
dinâmica, e polimorfismo.
Histórico
O primeiro protótipo, chamado de Chare Kernel(1.0), surgiu nos
anos 80. Levava em consideração apenas uma biblioteca de
construtos para invocação remota de métodos.
Após varios desenvolvimentos e versões
intermediárias, em 1993, surgiu o Charm 4.0. Logo após,
com o Charm 4.5, mudanças drásticas ocorreram no projeto
e que resultaram em uma reorganização da agenda de
pesquisa do Laboratório de Programação Paralela.
À época da escrita deste tutorial, o CHARM encontra-se na
versão 5.9, lançada em 03/09/2004. As chamadas foram
reescritas como construtos e macros em C++.
Objetivos
O desenvolvimento do CHARM++ foi realizado utilizando como base os
seguintes objetivos:
- Portabilidade Eficiente: Portabilidade é uma
característica essencial para o desenvolvimento de software
paralelo reutilizável.
- Tolerância à Latencia: A idéia de
que o acesso a um dado remoto levará tempo é uma
característica comum entre plataformas MIMD. Execução
dirigida a mensagens, suportado no Charm++, é um mecanismo
muito útil para tolerar e esconder esta latência. Em uma
execução oriendata a mensagens, um processador é
alocado para um processo apenas quando uma mensagen para o processo
é recebida. Isto significa que quando um processo bloqueia,
esperando por uma mensagem, outro processo pode ser executado no
processador. Isto também significa que um processo pode ser
bloqueado por qualquer número de mensagens distintas e
será acordado quando qualquer destas mensagens chegar. Isto
permite uma maneira eficiente de escalonar processos em um processador
na presença de latências potencialmente grandes.
- Balanceamento de Carga Dinâmico:
Criação e migração dinâmica de
trabalho é necessária em várias
aplicações. CHARM++ suporta isto fornecendo
estratégias de balanceamento dinâmica e estática.
- Reutilização e Modularidade: Deve ser
possível desenvolver um software paralelo reutilizando software
paralelo existente. CHARM++ suporte isto com um construto "module" bem
desenvolvido e mecanismos associados. Este mecanismo possibilita a
composição dos módulos sem sacrificar a
tolerância à latência. Com eles, dois
módulos, cada um espalhado sobre centenas de processadores,
pode trocar dados de maneira distribuída.
- Computação Regular e Irregular: Para
computação regular, o sistema é útil pois
fornece portabilidade, balanceamento estático de carga,
tolerância à latencia via execução orientada
a mensagens e facilidades para a contrução e
reutilização flexível de bibliotecas. Para
computação irregular, o sistema inclui o gerenciamento de
processos de granularidade média, suporte à
priorização, estratégias de balanceamento
dinâmico de carga, gerenciamento de estruturas de dados
dinâmicas, entre outras...
Comunicação
A comunicação entre os processos no Charm++ pode se dar
de 3 formas:
- Via rede: versão “net-”, que utiliza-se de UDP (TCP
é suportado), é mais lenta, mas mais compatível
- Via MPI (versão “mpi”)
- Via memória compartilhada
Visão Geral
Charm++ utiliza o conceito de programação orientada a
objeto, portanto, conceitos como objeto, métodos, construtores,
público/privado são pré-requisitos para o
entendimento da linguagem. A maior diferença para o C++ é
que um método pode ser
invocado para um processador remoto de forma assíncrona.
O processo que executou uma chamada não espera o método
ser executado
e não espera por valores de retorno,
a priori. Para referenciar-se aos
objetos, uma vez que podem estar fisicamente em outra máquina,
os programas utilizam-se de proxies.
Proxies e Handles
Para cada tipo chare existe uma classe proxy. O proxy de um objeto age
como um encaminhador (forwarder) de mensagens para o objeto.
Para invocar um método de um objeto, invoca-se o método
em seu proxy. Um objeto pode ter vários proxies.
Handles (que são na prática identificadores globais
únicos) foram historicamente utilizados para a referência
a objetos. Handles e proxies podem ser enviados em uma mensagen,
empacotados e desempacotados e terem seus parâmetros ajustados
para o sistema onde estão.
Modelo de Execução
Um programaCHARM++ é um conjunto de objetos CHARM++
distribuído entre o
número de processadores disponíveis.
Um chare é a unidade básica de computação
paralela em um
programa Charm++. É um objeto Charm++, que pode ser criado em
qualquer um dos pocessadores disponíveis e acessado por todos.
O sistema mantém um “work-pool” com sementes para novos chares e
mensagens para chares existentes.
O sistema runtime (Charm Kernel) pega estes itens, não
deterministicamente, e executa-os. Métodos que podem ser
invocados remotamente são chamados
“entry methods” e métodos comuns são não
preemptivos, isto é, o processador não vai parar o que
está fazendo para atender uma chamada a um método que
este (o processador) tenha recebido.
O próprio Charm++ fornece balanceamento dinâmico de seeds
e a localização de um chare remoto não precisa ser
conhecida para que este seja criado.
Chares podem migrar de processadores e cada programa possui ao
menos um “mainchare”.
Entidades definidas pelo usuário precisam ser registradas no
Charm Kernel, que fornece um identificador único para cada uma
(proxy).
Pode-se utilizar user-level threads e futures para melhorar as
funcionalidades básicas do Charm Kernel. Esses métodos
podem bloquear ao esperar por dados realizando
chamadas síncronas que retornarão resultados.
Entidades em um Programa Charm++
- Objetos seqüenciais: são
objetos comuns, presentes no C++.
- Mensagens: são os
objetos de comunicação do Charm++.
- Chares: são os objetos
concorrentes do Charm++.
- Chare Arrays: arrays
são conjuntos de chares, onde cada elemento é tratado
separadamente, isto é, cada elemento pode ser escalonado em um
processador.
- Chare Groups: são
grupos de chares, todos os membros do grupo devem ser executados em um
mesmo processador.
- Chare Nodegroups: parecido com
os chare groups. São grupos de chares que tem de ser executados
no mesmo nodo, isto é, se a máquina possuir mais de um
processador, os chares podem ser executados em qualquer um deles, mas
todos os chares têm que ser executados no mesmo nodo.
Estrutura de um programa Charm++
As interfaces para objetos Charm++ devem ser declaradas em um arquivo
de
interfaces “.ci”. O tradutor de interface pega este arquivo e produz 2
outros com sufixos
“.decl.h” e “.def.h” para cada módulo declarado no “.ci”. Decl e
def significam, respectivamente, declarações e
definições.
Exemplo de arquivo .ci
mainmodule hello {
readonly CProxy_Main mainProxy;
readonly int nElements;
mainchare Main {
entry Main(CkArgMsg *m);
entry void done(void);
};
array [1D] Hello {
entry Hello(void);
entry void SayHi(int hiNo);
};
};
Exemplo de arquivo .h
#include "Hello.decl.h" // Note: not pgm.decl.h
class HelloMain: public Chare {
public:
HelloMain(CkArgMsg *);
void PrintDone(void);
private:
int count;
};
class HelloGroup: public Group {
public:
HelloGroup(void);
};
Exemplo de arquivo .C
#include "pgm.h"
CProxy_HelloMain mainProxy;
HelloMain::HelloMain(CkArgMsg *msg) {
delete msg;
count = 0;
mainProxy=thishandle;
CProxy_HelloGroup::ckNew(); // Create a new
"HelloGroup"
}
void HelloMain::PrintDone(void) {
count++;
if (count == CkNumPes()) { // Wait for all group
members to finish the printf
CkExit();
}
}
HelloGroup::HelloGroup(void) {
ckout << "Hello World from processor "
<< CkMyPe() << endl;
mainProxy.PrintDone();
}
#include "Hello.def.h" // Include the Charm++ object implementations
Versões Disponíveis do CHARM++
Para poder utilizar o CHARM++, tanto se o usuário optar por
baixar uma versão binária pré-compilada, como se
optar por obter o código fonte e compilá-lo em sua
máquina, é fundamental escolher a versão correta a
ser utilizada.
Abaixo encontra-se uma tabela que explica as versões existentes.
Esta não é a tabela completa, ela poderá ser
obtida executando-se o comando
build no
diretório onde o CHARM++ foi descompactado, após a
versão correta ter sido baixada.
Versão Charm
|
SO
|
Comunicação
|
Compilador Padrão
|
| net-linux |
PC Linux |
UDP/Myrinet |
|
| net-sol |
Solaris |
UDP |
GNU |
| net-win32 |
Win32 |
UDP |
GNU |
| net-cygwin |
Win32/cygwin |
UDP |
MS
Visual C++ |
| mpi-sp |
IBM A/IX |
MPI |
GNU |
| mpi-origin |
Origin2000 |
MPI |
A/IX
xlC |
| net-ppc-darwin |
MacOS X |
UDP |
SGI C++ |
| net-linux-ia64 |
IA64 Linux |
UDP/Myrinet |
GNU C++ |
| net-irix |
IRIX |
UDP |
GNU |
| net-axp |
Alpha |
UDP |
GNU |
| net-hp |
HP-UX |
UDP |
GNU |
| mpi-linux |
PC Linux |
MPI |
GNU |
| mpi-linux-ia64 |
IA64 Linux |
MPI |
GNU |
| mpi-axp |
Alpha |
MPI |
GNU |
| mpi-linux-axp |
Alpha Linux |
MPI |
GNU |
| origin2000 |
Origin2000 |
shared-mem |
SGI C++ |
| t3e |
Cray T3E |
shared-mem |
Cray C++ |
Para escolher a versão, deve-se atentar para três aspectos:
- A forma que o programa paralelo escrito em CHARM++
utilizará para comunicar-se. Os parâmetros válidos
são:
- "net-" O CHARM++ irá comunicar-se utilizando a
família de protocolos TCP/IP, por padrão, através
de pacotes UDP. Este tipo é mais lento que as outras
opções, mas funciona em qualquer lugar.
- "mpi-" O CHARM++ irá comunicar-se utilizando chamadas MPI.
Máquinas com boa implementação MPI podem usufruir
de ganhos sensíveis com esta abordagem (ex.: Origin 2000).
- "exemplar", "ncube-2", "paragon-red", "sp3" e
"t3e" O CHARM++ irá comunicar-se utilizando chamadas diretas
para as primitivas de comunicação da máquina
destino.
- "sim-" e "uth-" não são mais ativamente mantidas.
Estas são versões para ambientes com um único
processador: "uth-" simula os processadosres com threads em nível de
usuário e "sim-" realiza troca entre processos e conta realiza a
contagem das comunicações.
- O seu Sistema Operacional. Os parâmetros válidos
são:
- "linux" GNU Linux
- "win32" MS Windows com compilador MS Visual C++
- "cygwin" MS Windows com camada Unix Cygnus Cygwin
- "irix" SGI IRIX
- "origin" SGI Origin 2000 IRIX
- "sol" Solaris
- "sun" SunOS
- "rs6k" IBM R/S 6000 A/IX
- "sp" IBM SP A/IX
- "hp" Hewlett-Packard
HP-UX
- "axp" DEC Alpha DECUNIX
- Em alguns casos apenas, a arquitetura na qual o sistema
está sendo executado também deve ser especificada. Os
parâmetros válidos são:
- "-x86" Para Solaris, utiliza hardware PC
ao invés de hardware Sun.
- "-axp" Para Linux, utiliza hardware Alpha ao invés de
hardware PC.
- "-ia64" Utiliza instruções Itanium(tm).
A versão do CHARM++ é obtida através da
concatenação das opções.
Ex.:
- "net-linux" CHARM++ para uma rede de estações Linux
compilado utilizando g++.
- "mpi-origin" CHARM++ para SGI Origin 2000 compilado usando SGI CC.
Obtendo o Charm++
O Charm++ pode ser obtido no endereço
http://charm.cs.uiuc.edu/download.html,
após um registro, ou diretamente em
http://charm.cs.uiuc.edu/download/downloads.phtml.
Há versões em código fonte (tar.gz ou CVS) e
binários pré-compilados.
Se a versão escolhida foi binária, execute os passos 1 e
2; já se foi código fonte, realize os passoa, 1, 2 e 3
para a instalação do Charm++.
- Baixar o arquivo .tar.gz correspondente (veja seção
abaixo, para saber qual a versão mais adequada ao seu caso).
- Descompactar o arquivo com uma ferramenta que entenda o formato
tar.gz (tar em Unix-like, Winzip ou Winrar no Windows®).
- Se a versão escolhida foi a de código fonte, mude
para o diretório onde os arquivos foram descompactados e execute
o comando build com os
parâmetros target e version (ex.: ./build charm++
net-linux).
Após a instalação, a seguinte estrutura de
diretórios existirá no local onde o Charm++ foi instalado.
- charm/
- bin/ Arquivos
executáveis utilizados pelo CHARM++, como charmc, charmrun e charmrun-silent.
- doc/ Arquivos de
documentação do CHARM++, nos formatos código fonte
LaTeX, HTML e PDF.
- include/ Arquivos de
include (.h) do CHARM++ C++.
- lib/ Arquivos de
bibliotecas (.a) do CHARM++.
- pgms/ Programas
exemplos em CHARM++.
- src/ Código
fonte do CHARM++.
- tmp/ Diretório
onde o CHARM++ é construído.
- tools/ Ferramentas de
visualização para programas CHARM++.
Para o comando build, a seguinte sintaxe deve ser observada:
build
<target> <version> [options ...] [--basedir=dir]
[--libdir=dir] [--incdir=dir] [charmc-options ...]
Onde:
- <target> informa a parte do CHARM++ a ser compilada.
Opções válidas são:
- "charm++" Irá compilar os executáveis do CHARM++
e bibliotecas.
- "AMPI" Uma implementação do MPI rodando sobre o
CHARM++.
- "FEM" Um framework de Elementos Finitos rodando sobre o Charm++.
- <versions> define a CPU, o SO e o meio de
comunicação que as máquinas utilizarão.
Veja Versões
Disponíveis do CHARM++, acima.
- <options> define outras opções de
compilação do CHARM++, entre as quais:
- Para escolher o compilador:
- gcc3 GNU GCC/G++ versão 3
- acc Compilador HP aC++
- cc Compilador Sun WorkShop C++
- cc64 Compilador 64 bits Sun WorkShop C++ ou IBM xlC
- cxx Compilador DIGITAL C++
- kcc Compilador KAI C++
- pgcc Compilador Portland Group's C++
- icc Compilador Intel C/C++ para Linux IA32
- ecc Compilador Intel C/C++ para Linux IA64
- mpcc Compilador SUN Solaris C++ para MPI.
- tcp Para a opçao "net-", faz com que o protocolo
utilizado passe a ser o TCP ao invés do UDP.
- smp Habilita o suporte direto a SMP. Uma versão
"smp"
comunica-se utilizando memória compartilhada para
comunicações dentro
da máquina e através de mensagens normais para mensagens
entre máquinas.
- bluegene Compila o CHARM++ para ser executado em um emulador
Blue Gene.
- help Mostra as opções suportadas para um
versão. Por exemplo: ./build charm++
net-linux help dará as opções suportadas,
neste caso:
gcc3, gm, icc, kcc, pgcc, scyld, smp, bluegene e tcp.
- [--libdir=dir] Caminho adicinal para bibliotecas utilizadas na
construção do CHARM++.
- [--incdir=dir] Caminho adicional para os includes utilizadas na
contrução do CHARM++.
- [--basedir=dir] Um atalho pra espeficicar as bibliotecas e includes na
construção do CHARM++. O path dos include será dir/include e o
path das bibliotecas
será dir/lib.
Compilando um programa
O CHARM++ vem com vários programas de exemplo. Estes programas
encontram-se dispostos em subdiretórios dentro do
diretório pgms/charm++.
Para executar um programa, o usuário pdeve entrar em um desses
diretórios e executar o comando
make. O
programa será então construído e um
executável "pgm" será gerado.
Algumas opções podem ser passadas para o compilador. Para
isso, basta editar o arquivo Makefile existente no diretório ou
definir a variável OPTS. Dentre as opções
disponíveis para serem informadas ao compilador, destacam-se as
abaixo:
- "-I" Adiciona um diretório ao caminho de procura por
arquivos include.
- "-g" Adiciona informações de debug nos arquivos
compilados.
- "-L" Adiciona um diretório ao caminho de procura por
arquivos de biblioteca especificados pelo comando -l.
- "-l" Especifica as bibliotecas a serem lingadas (linked).
- "-module m1[,m2[,...]]" Especifica módulos adicionais para
serem ligados. É similar ao =l, mas registra também
objetos paralelos CHARM++.
- "-O" Arquivo será compilado com a máxima
otimização.
- "-NO" Se utilizado com o -O, habilita retrocompatibilidade.
- "-verbose" Todos os comandos executados pelo charmc são
direcionados para stdout.
- "-seq" Indica que código seqüencial está sendo
compilado.
- "-balance seed load-balance-strategy" Biblioteca de balanceamento
de carga. Há atualmente três suportadas: rand, test e
neighbor. A padrão é "-balance rand".
- "-tracemode tracing-mode" Informa o nível de tracing para
programas CHARM++. Há atualmente três parâmetros
suportados: none, summary e projections. A padrão é
"-tracemode none".
Executando um programa
Para executar um programa, deve-se utilizar o executável
charmrun,
que fornece uma interface padrão para os programas CHARM++,
seguido pelo nome do arquivo e por parâmetros, caso estes sejam
necessários.
Por exemplo, o comando
./charmrun ./pgm +p2 executará o programa pgm
gerado anteriomente utilizando-se de 2 processadores.
Em algumas plataformas
charmrun é
apenas um script shell que chama um programa para uma platavorma
específica (ex.: mpirun para programas mpi).
Para versões "net-",
charmrun é
um executável que invoca rsh ou ssh para iniciar programas em
máquinas remotas. Você deveria configurar um arquivo
.nodelist, que enumera as máquinas que devem ser utilizadas na
execução dos jobs, caso contrário, um arquivo
será criado contendo apenas o localhost.
Um exemplo típico de um arquivo .nodelist é mostrado
abaixo:
group main ++shell /bin/ssh
host <machinename>
O programa padrão de shell é o rsh, mas pode-se utilizar
ssh. Em ambos os casos, deve-se ter direitos de executar o ssh ou rsh
na máquina destino sem a necessidade de senha.
Como forma de auxiliar na realização de testes, as
versões "net-" do charmrun vêm com a opção
"++local". Assim, não haverá invocação de
shell remoto e apenas a máquina local será utilizada.
O programa charmrun aceita os seguintes parâmetros:
- "+pN" Executa o programa em N processadores, onde o padrão
de N é 1.
- "+ss" Imprime estatísticas de sumário sobre a
criação chare. Esta opção imprime o
número total de requisições de
criação de chare realizados e o número total de
requisições de criação de chare processadas
entre todos os processadores.
- "+cs" Imprime estatíticas sobre o número de
mensagens chare requisitadas e processadas, o número de
mensagens para chare requisitadas e processadas e o número de
mensagens para branch office chares
requisitadas e processadas, isso tudo separado por pocessador. Note que
o número de mensagens criadas e processadas para um tipo
particular de mensagem em um dado nodo pode não ser o mesmo,
já que uma mensagem pode ser processada pro um processadore
diferente do qual a mensagem se originou.
- "user_options" Opções podem ser passadas ao
programa que vai ser executado misturadas com as opções
do charmrun.
Entretanto, as opções do programa não podem
iniciar com +. As opções serão passadas como
argumentos para o programa através da construção
tradicional argc/argv.
Programas construídos usando a versão de rede do CHARM++
podem ser executados sem a utilização do
charmrun.
Isto restringe o uso dos processadores para a máquina local, mas
é conveniente e freqüentemente utilizado para debug. Por
exemplo, um programa CHARM++ pode ser executado com um debugger,
utilizando-se o comando
gdb pgm.
As seguintes opções estão ainda disponíveis
na versão de rede do CHARM++:
- "++local" Executa o programa somente na máquina local.
Não há invocação remota de shell neste
caso. Isto é útil para rodar pequenos porgramas em apenas
uma máquina.
- "++debug" Executa cada nodo sob gdb em uma janela xterm,
questionando o usuário pelo início da
execução.
- "++debug-no-pause" O mesmo que ao anterior, apenas não
espera a intervenção do usuãrio para o
início da execução.
- "++maxrsh" Número máximo de shells remotos (rsh) a serem
executados.
- "++nodelist" Arquivo contendo a lista de nodos.
- "++help" Imprime a mensagem de ajuda.
- "++runscript" Script com o qual o programa será executado.
- "++xterm" Informa que xterm utilizar.
- "++in-xterm" Executa cada nodo em uma janela xterm.
- "++display" O display X para o xterm.
- "++debugger" Informa o debugger que deverá ser utilizado.
- "++remote-shell" Informa o shell remoto que deverá ser
utilizado".
- "++useip" Usa o endereço ip fornecido para o charmrun.
- "++usehostname" Envia para os nodos o hostname
simbólico ao invés do endereço IP.
- "++server-auth" Arquivo de autenticação CCS.
- "++server-port" Porta para escutar as requisições
CCS.
- "++server" Habilite o modo Cliente-Servidor.
- "++nodegroup" Qual grupo de nodos deve ser utilizado.
- "++verbose" Imprime mensagem de diagnóstico.
- "++timeout" segundos a esperar para uma conexão com o host.
- "++p" Número de processos a criar.