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:
  1. Portabilidade Eficiente: Portabilidade é uma característica essencial para o desenvolvimento de software paralelo reutilizável. 
  2. 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.
  3. 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.
  4. 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.
  5. 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:

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++


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:
  1. A forma que o programa paralelo escrito em CHARM++ utilizará para comunicar-se. Os parâmetros válidos são:
  1. O seu Sistema Operacional. Os parâmetros válidos são:
  1. 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:
A versão do CHARM++ é obtida através da concatenação das opções.

Ex.:

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++.
  1. Baixar o arquivo .tar.gz correspondente (veja seção abaixo, para saber qual a versão mais adequada ao seu caso).
  2. Descompactar o arquivo com uma ferramenta que entenda o formato tar.gz (tar em Unix-like, Winzip ou Winrar no Windows®).
  3. 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.

Para o comando build, a seguinte sintaxe deve ser observada:
build <target> <version> [options ...] [--basedir=dir] [--libdir=dir] [--incdir=dir] [charmc-options ...]

Onde:

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:


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:
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++: