# Programação Paralela em Arquiteturas Multi-Core/Arquitetura Sun Niagara e Niagara II

A arquitetura Niagara e Niagara II foi projetada para prover uma solução de alto desempenho para servidores de aplicações comerciais. Ela suporta 32 threads e explora o paralelismo a nível de threads inerente às aplicações de servidor combinado a baixos níveis de consumo de energia.
O texto abaixo apresenta essa nova abordagem e seus principais recursos. Primeiro é apresentado uma introdução justificando a abordagem adotada no projeto do Niagara. Depois será apresentado os detalhes técnicos da arquitetura Niagara, as suas limitações e as melhorias do Niagara 2. Na próxima seção será apresentado as aplicações, sistemas operacionais, linguagens e modelos de programação compatíveis com essa arquitetura. Por últimos, as conclusões e um pouco sobre o futuro do Niagara.

### Introdução

Ao longo das duas últimas décadas, os projetistas de microprocessadores têm focado na melhoria do desempenho de uma única thread, aumentando a freqüência e explorando paralelismo a nível de instrução (ILP), utilizando técnicas tais como execução de várias instruções, execução fora de ordem e previsão de branches. A ênfase na melhoria do desempenho de uma única thread tem apresentado cada vez menos resultados devido às limitações em termos de latência de memória principal e as inerentes aplições com baixo ILP. Isto levou a uma explosão de complexidade nos projetos dos microprocessadores e tornou a dissipação de calor uma grande preocupação.
Por estas razões, o processador Niagara da Sun Microsystems apresenta uma abordagem radicalmente diferente para o projeto de um novo micropocessador. Ao invés de focar sobre o desempenho de uma única ou duas threads, a Sun otimizou o Niagara para um obter desempenho em servidores comerciais multithread. Essa abordagem aumenta o desempenho da aplicação melhorando o throughput. Isso é especialmente eficiente em servidores comerciais de aplicações como banco de dados e serviços Web, no qual tendem ter cargas de trabalho contendo grande quantidade de paralelismo a nível de threads (TLP).
O Niagara é uma implementação totalmente nova da arquitetura do Sparc V9, que explora uma grande quantidade de paralelismo on-chip para prover um alto throughput. O Niagara suporta 32 threads de hardware combinando idéias de multiprocessor chips e multithreading. Vários estudo vem indicando um significante ganho de desempenho utilizando essa abordagem cargas de trabalho multithread.
A execução eficiente de várias threads esconde a latência da memória. Entretanto, ter 32 threads exigem do sistema de memória a necessidade de uma alta largura de banda. Quatro controladores de memória independentes dentro do Niagara conseguem prover cerca de 20 Gbytes/s de largura de banda para a memória.
A utilização de TLP tambem permite aumentar significantemente o desempenho sem precisar melhorar a frequência do clock da CPU. Isso e a utilização de pipelines entre múltiplas threads permitem um projeto eficiente em termos de área e de consumo de energia. Os projetistas calculam que o Niagaram dissipa cerca de 60 W de energia, tornando-o muito atrativo para ambientes com alta densidade de computação. Nos date centers, por exemplo, os custos com o consumo de energia e ar condicionado estão se tornando muito significantes.
A métrica de desempenho chave para servidores comerciais, como os servidores Web, é a quantidade de requisições atendidas em um intervalo de tempo. Mas outros requisitos, como consumo de energia e ar condicionado, devem ser levados em conta. A utilização de processadores ILP em clusters de servidores funcionando a clocks menores pode ser uma solução. No entanto, o desempenho é diretamente proporcional à frequência do clock e isso faz com que esses processadores apresentem uma perda proporcional no desempenho, tornando essa solução menos desejada. Essa situação motiva a outros requisitos para melhorar o desempenho por Watt. Esses requisitos não são atendidos eficientemente utilizando máquinas otimizadas para o desempenho de uma única thread.
Aplicações para servidores comerciais tendem a ter um baixo ILP porque eles tem grandes conjuntos de trabalhos e pouca localidade de referência no acesso a memória; todos os dois contribuem para altas taxas de cache miss. Além disso, branches com dados dependentes são difíceis de prever, assim o processador tem que discartar o trabalho feito na direção errada. A combinação de um baixo ILP disponível e uma alta taxa de cache miss faz com que o acesso a memória seja uma limitação para o desempenho.
As aplicações de servidores tendem a ter grande quantidade de TLP. As máquinas de memória compartilhada com processadores de uma thread interconectados possuem um bom desempenho justamente porque exploram o TLP. Entretanto, a utilização de SMP junto composto de vários processadores de uma thread não possuem uma relação custo-benefício tanto para o consumo de energia quanto em termos de custo. Uma abordagem mais eficiente é construir uma máquina composta de cores simples agregados em um único die, com uma cache compartilhada e alta largura de banda com a memória, ou seja, produzindo um SMP server dentro de um chip.
O Niagara I representa o primeiro de uma série de microprocessadores que a Sun desenvolverá com várias threads de hardware para prover alto throughput e alto desempenho por Watt em aplicações de servidores comerciais. A disponibilidade de uma arquitetura rica em threads abre novas oportunidades para os desenvolvedores aumentarem o desempenho das suas aplicações. Essa arquitetura é uma mudança de paradigma no projeto de microprocessadores. Após o lançamento do Niagara, a Sun conseguiu sair do vermelho e está faturando alto com as vendas do Sparc T1 (nome comercial da máquina que utiliza o Niagara).

### Arquitetura

O processador Niagara SPARC é projetado para um baixo cosumo de energia e a alto throughput para servidores comerciais de aplicações, onde o consumo de energia, o resfriamento e o espaço são as principais preocupações. A arquitetura de multithreaded chip (CMT) alcança altos throughput enquanto otimiza o desempenho por watt. A execução concorrente de 32 threads é implementada através de 8 4-way multithreaded cores simétricos, associados a um sistema de cache/memória de baixa latência e alta largura de banda.
A firgura abaixo apresenta o pipeline do Niagara.
A abordagem do Niagara para aumentar o throughput nas aplicações de servidores comerciais envolve um grande aumento no número de threads suportados pelo processador e subsistema de memória que deve suportar uma grande largura de banda. O Niagara suporta 32 threads de execução em hardware. A arquitetura organiza cada quatro threads em um grupo de thredas. Esse grupo compartilha o mesmo pipeline de processamento, referenciado como Sparc Pipe. O Niagara possui 8 grupos de threads na CPU. Cada Sparc Pipe possui uma cache L1 para instruções e dados. O hardware esconde os stalls da memória e do pipeline de uma dada thread escalonando outras threads de um mesmo grupo no Sparc pipe com uma penalidade de zero ciclos.
As 32 threads compartilham uma cache L2 de 3MB. Nos sistemas SMP convencionais que utilizam processadores discretos com sistema de interconexão coerente, misses de coerência são frequentes sobre os barramentos de baixa frequencia, além de ter altas latências. O Niagara possui uma cache interna ao chip que elimina tais misses e possui uma baixa latência para comunicação entre as caches.
A interconexão via crossbar provê uma comunicação entre os Sparc pipes, bancos de cache L2 e outros recursos compartilhados na CPU. Ele provê mais de 200 GB/s de largura de banda. O crossbar também provê uma porta para a comunicação com o subsistema de E/S. A interface de memória é de quatro canais de DDR2 DRAM, suportando uma largura de banda máxima de 20 GB/s e uma capacidade de 128 GB.

#### Formatos de Dados

A arquitetura Niagara reconhece os seguintes tipos de dados: inteiro com sinal (8, 16, 32 e 64 bits), inteiro sem sinal (8, 16, 32 e 64 bits), SIMD (Uint8 de 32 bits, Int16 de 64 bits, Int32 de 64 bits) e ponto flutuante (32, 64 e 128 bits). Acompanham esses tipos de dados, os seguintes comprimentos de palavras: byte (8 bits), meia-palavra (16 bits), palavra (32 bits), palavra-marcada (valor de 30 bits e 2 bits de marcação), palavra dupla/extendida (64 bits) e palavra quádrupla (128 bits). Os inteiros com sinal são escritos em complemento-de-dois, enquanto os inteiros sem sinal são escritos usando-se toda sua extensão. Palavras-marcadas têm a marcação nos dois bits menos significativos. Pontos flutuantes têm sua representação normal (IEEE Std 754-1985), alterando apenas os expoentes ({7;0}, {10;0} e {14;0}) e frações ({22;0}, {52;32}+{31;0} e {111;96}+{95;64}+{63;32}+{31;0}), respectivamente para 32, 64 e 128 bits de representação, todas com um bit de sinal.

#### Registradores

São vários os tipos de registradores utilizados na arquitetura UltraSPARC:
* uso geral: incluem os registradores de passagem de parâmetro, variáveis locais e globais. Podem ser implementadas janelas de registradores, nas quais os registradores de entrada de uma janela correspondem aos de saída de outra janela, interligando-as. De modo geral, pode haver de 72 a 640 registradores do tipo R em um processador UltraSPARC, sendo que R[0] sempre mantém seu valor em zero, e o comando CALL escreve seu próprio endereço em R[15];
* ponto flutuante: são 64 registradores de 32 bits, que podem ser acessados como 16 de 128 bits para quádrupla precisão (FQ[0], FQ[4],..., FQ[60]) ou 32 de 64 bits para precisão dupla (FD[0, FD[2],...].,FD[62]) ou ainda 32 de 32 bits para precisão simples (FS[0], FS[1],...,FS[31]) e onde se utiliza apenas a primeira metade dos registradores. Há também um registrador especial com grupos de bits para indicar estados especiais dos registradores de ponto flutuante, como arredondamento e exceções;
* auxiliares de estado: são registradores utilizados para indicar resultados como divisões ou multiplicações de 32 bits, códigos para operações com inteiros (condition codes), contador de ciclos (ticks), disparador de interrupção ao atingir um determinado valor de ciclos (ticks), program counter, e outros. Há registradores especiais reservados para implementações e cada processador virtual conta com seu próprio conjunto de registradores auxiliares, que não são compartilhados;
* estado de janelas de registradores: há um conjunto especial de registradores que controlam qual a janela ativa, se podem ser salvas ou restauradas, se podem ser esvaziadas, etc. Os comandos que lidam com esses registradores são de acesso privilegiado ou hiperprivilegiado. Há um outro conjunto semelhante que só pode ser visto por processos privilegiados, e cada processador virtual conta com um conjunto próprio deles;
* hiperprivilegiados: somente podem ser acessados por processos com esse nível, e contém registradores que informam sobre os estados dos registradores, interrupções, traps e ciclos (ticks).

#### Instruções

As instruções da arquitetura Niagara são lidas individualmente pelos processadores virtuais e executadas (ou trapped ou anuladas). Há um registrador chamado NPC (next program counter), que recebe o valor de PC+4 ou um outro valor, caso haja um salto ou transferência de controle, de acordo com o que ocorrer com a instrução. Todas instruções são divididas em 4 formatos e 11 categorias, totalizando 111 instruções.
Os 4 formatos são ajustados conforme os dois primeiros bits mais significativos: 00 para operações SETHI e branches, 01 para loads e 10 e 11 para aritmética, lógica, traps, loads, stores e outros. As 11 categorias são acesso à memória (usam 1 ou 2 registradores tipo R para calcular um endereço de memória de 64 bits de espaço e 8 bits de comprimento, além de alinhamento), sincronização de memória (ordem em que são feitos loads/stores), aritmética de inteiros e/ou lógica (shifts, multiplicações, divisões, etc.), transferência de controle (branches, traps, calls e jumps), movimentos condicionais (mover valores de um registrador para outro com certas condições ou comparar registradores, eliminando branches), gerenciamento de janelas de registradores (salvar, restaurar, etc.), acesso a registradores auxiliares (escrita e leitura) e acesso a registradores privilegiados (também de escrita e leitura), operações de ponto flutuante, instruções dependentes da implementação e instruções reservadas.

#### Memória

Com capacidade para executar 32 threads simultaneamente, uma interface rápida de acesso a memória é extremamente importante para o Niagara. Para suprir a alta demanda de dados, o processador conta com quatro controladores de memória DDR2 integrados. Ao integrar o controlador de memória com o processador, fazemos com que ele funcione na mesma frequência do processador, recebendo informações da memória muito mais rapidamente.

##### ASI

Cada localidade da memória é referenciada por um adress space identifier (ASI) de 8 bits e um endereço de memória de 64 bits. O ASI é utilizado para identificar em que tipo de espaço da memória um endereço se encontra (memória principal, memória secundária, etc.). Além disto, ele fornece um atributo que é único para cada endereço, mapeia registradores internos de um processador virtual e define o formato de acesso a dados (little ou big-endian).
Existem três categorias para um ASI, que definem como a MMU vai tratar o endereço de memória:
* Virtual-Translating ASI: O endereço de memória é tratado como virtual e traduzido pela MMU em um endereço físico.
* Non-Translating ASI: O endereço não é modificado pela MMU. Este tipo de ASI é usado geralmente para acessar registradores internos.
* Real-Translating ASI: O endereço de memória é tratado como real e traduzido pela MMU em um endereço físico. É utilizado por softwares com altos privilégios para acessar diretamente a memória utilizando endereços reais ou físicos

##### Cache

Um dos principais limitadores à velocidade de processamento é o tempo de latência das memórias, que é o tempo despendido desde a solicitação de dados pelo processador até o recebimento dos mesmos. Para diminuir os efeitos deste problema, existem os caches: memórias pequenas e rápidas, em um nível mais próximo ao processador, que servem para armazenar os dados e instruções com maior probabilidade de serem solicitados pelo processador. Cada core do processador Sun Niagara possui um cache L1 de 24kB, dividido em um cache de instruções de 16kB, com blocos de 32B e um cache de dados de 8kB, com blocos de 16B, ambos four-way set associative. O processador conta ainda com um cache L2 de 3MB, twelve-way set associative, com blocos de 64B, que é compartilhado entre os 8 cores. Para manter a coerência de cache, é utilizada a técnica de write-through.
Se comparado com processadores atuais single-thread, estes caches são pequenos, porém para a arquitetura multicore, multithread do Niagara eles são suficientes. Caches reduzidos também são necessários pelo espaço limitado do die, devido aos múltiplos cores. Quando um processador single-core executa uma única thread e ocorre um cache miss, é necessário esperar até que os dados requisitados sejam obtidos da memória principal. Uma das soluções para este problema é a execução de instruções fora de ordem, mas isto leva a uma maior complexidade do chip. No caso do Sun Niagara, no entanto, não há suporte a esta tecnologia, porém por se tratar de um processador multithread, no caso de precisar buscar dados da memória podemos simplesmente mudar a thread que está sendo executada para outra e aguardar até que os dados sejam obtidos, minimizando os efeitos de um cache miss. Como as penalidades para cache miss são baixas, o processador não precisa depender tanto de técnicas de branch prediction, que unido à falta de suporte à execução de instruções fora de ordem, colabora para tornar a arquitetura mais simples.

##### Crossbar

Para que mais de um core possa ter acesso ao cache L2 simultaneamente, ele é dividido em 4 banks. Interconectando os banks do cache L2, os controladores de memória e a unidade de ponto flutuante aos cores, existe uma estrutura chamada Crossbar Switch. O crossbar tem uma taxa de transferência altíssima, cerca de 200GB/s.

#### Sparc Pipe

Nesta seção será apresentado a implementação do Sparc pipe, que suporta quatro threads. Cada thread possui um conjunto único de registradores, buffer de instruções e de dados. Cada grupo de threads compartilham a cache L1, a TLB, unidades de execução e a maioria dos registradores do pipeline.
Foi implementado um pipeline de propósito único com seis estágios: fetch, thread select, decode, execute, memory e write back). No estágio fetch, a cache de instrução e a TLB de instrução (ITLB) são acessados. O multiplexador de seleção de threads escolhe o contador de programa (PC) de uma das threads, dentre os quatro disponíveis, que executará o acesso. O pipeline busca duas instruções por ciclo. Um bit pré-codificado na cache indica as instruções de latência alta.
No estágio de thread-select, o multiplexador de seleção de threads escolhe uma thread dentre as disponíveis para executar uma instrução nos estágios seguintes. Esse estágio também mantém um buffer de instruções. As instruções trazidas da cache na estágio fetch podem ser inseridas no buffer de instrução daquela thread se os estágios seguintes estiverem ocupados. Os registradores do pipeline para os dois primeiros estágios são replicados para cada thread.
As instruções da thread selecionada vai para o estágio decode, no qual executa a decodificação da instrução e acessa o register file. As unidades de execução suportadas incluem uma unidade de lógica aritimética (ALU), um shifter, um multiplicador e um divisor. Uma unidade intermediária fica a cargo de passar os resultados para as instruções dependentes ante de atualizar o register file. A instruções da ALU e do shifter possuem latência de um único ciclo e geram os resultados no estágio execute. As operações de multiplicação e divisão possuem longa latência e causam a troca de thread. A unidade de load-store contém a TLB de dados (DTLB), a cache de dados e buffers de armazenamento. Os acessos a cache de dados e a DTLB são feitos no estágio de memória. As instruções de um único ciclo (i.e. ADD) atualizam o register file no estágio write back.
A lógica de seleção de threads decide qual thread está ativa em um dado ciclo nos estágios de fetch e thread-select. Consequentemente, se o estágio thread-select escolhe uma thread para executar um instrução no estágio de decodificação, o estágio fetch tambem seleciona a mesma instrução para acessar a cache. A lógica de seleção de threads utiliza informações de vários estágios de pipeline para decidir quando selecionar ou deselecionar uma thread. Por exemplo, o estágio thread-select pode determinar o tipo da instrução utilizando um bit pré-decodificado na cache de instrução, enquanto algumas traps somente podem ser detectadas em estágios mais avançados do pipeline. Consequentemente, um tipo de instrução pode causar a deseleção de uma thread no estágio thread-select, enquanto uma trap detectada no estágio memory necessita limpar todas as próximas instruções de uma thread e deselecionar ela mesma duranto o processamento da trap.

#### Política de Seleção de Thread

A política de seleção de thread consiste em intercalar as threads disponíveis a cada ciclo, dando prioridade para a thread menos recentemente usada. As threads podem ficar insdiponíveis por causa de instruções de latência longa como loads, branches, multiplicação e divisão. Elas tambem ficam indisponíveis por causa de stall no pipeline causados por cache misses, traps e concorrência por recursos.
O escalonador de threads assume que todos os load são cache hit and que, consequentemente, provêem um instrução dependente para a execução na mesma thread de maneira especulativa. Entretanto, essa thread especulativa é atribuída com uma prioridade menor na hora de apresentar uma nova instrução comparado com uma outra thread que pode prover uma instrução não-especulativa.

#### Limitações

A arquitetura Niagara também apresenta as suas limitações, por isso a Sun já lançou o Niagara II e está em desenvolvendo o projeto Rock com o objetivo de melhorar aida mais o desempenho dessa arquitetura. A primeira limitação é a integração da E/S dentro do processador, que impôs um throughput máximo. A seguir estão os númerod do Niagara:
* PCIe throughput = 2.0 GB/second
* 10Gb Ethernet throughput = 1.25 GB/sec
O Throughput total do Niagara para E/S é igual a 2.0 mais 1.25, que totaliza 3.25 GB/sec. Enquanto isso os seus concorrentes, IBM e HP, possuem um throughput máximo de 20 GB/s e 8.5 GB/s respectivamente.
Além disso, o Niagara, como já foi dito anteriormente, foi otimizado para aplicações que utilizam multithread e para outros tipos de aplicações ele não adequado. Um outra limitação é que o Niagara possui somente uma unidade de ponto flutuante para todos os cores. Assim, uma aplicação que possui muitas instruções que demandam essa unidade, fazem com que a mesma se torne um gargalo. Por último, o Niagara apresenta somente sistemas com um processador, limitando os ambientes empresariais a uma escalabilidade vertical.
Obviamente, que as limitações apresentadas dependem muito da carga de trabalho utilizada. Para sistemas comerciais multithread e com baixa E/S, o Niagara fica muito além dos seus concorrentes. No entanto, ele não atende aos outros tipos de aplicação.

#### Niagara 2

A segunda versão do Niagara possui uma série de aprimoramentos com o objetivo de diminuir as limitações apresentadas anteriormente. As suas principais melhorias são:
* o Sparc pipe foi modificado e agora consegue processar duas threads simultaneamente, o que totalizam 64 threads de hardware;
* possui 2 portas de 10Gbits incorporadas, o novo modelo permite acesso rápido e fácil comunicação entre os servidores;
* foram adicionadas oito unidades de aceleração criptográfica e um total de 10 funções independentes mapeiam todas as necessidades de segurança, sem comprometer o desempenho do equipamento;
* oito unidades de pontos flutuantes, uma unidade por core, maximizam os benefícios do CMT(Chip Multi Thread) para computação de alta performance;
* agora são oito vias de aplicações rápidas PCI Express como mídia streaming, escrita e leitura de banco de dados e backup de informações;
* o clock aumenteou de 1.4 GHz para 1.2 GHz;
* a cache L2 aumentou de 3 para 4 MB;
* o escalonamento de thread e o prefetching de instrução foram melhorados atingindo um desepenho melhor na execução das threads;
* utilização duas ALUs por core
* quatro controladores de memória dual-channel FBDIMM;
* feito utilizando o processo de 65nm.

### Aplicações

A arquitetura Niagara é considerada ecológica por possuir um baixo consumo de energia em comparação com as outras arquiteturas. Além disso, muitas empresas estão de olho nela, pois o custos por watt e de ar condicionado estão cada vez maiores. Dado que o Niagara possui um ótimo desempenho como servidor Web e o número de usuários de internet cresce cerca de 300 milhões ao ano, qualquer Watt econimizado em uma arquitetura de servidor faz uma enorme diferença.
Para desenvolver aplicações para arquiteturas chip multithreads (CMT) várias bibliotecas podem ser utilizadas. A Sun disponibilizou o documento Improving Application Efficiency Through Chip Multi-Threading. Nele são apresentadas as técnicas, bibliotecas e diretrizes para programar nesse tipo de arquitetura.
Podem ser utilizados Pthreads ou Solaris threads (disponível somente no sistema operacional Solaris). Além das interface comum para threads como a utilização de mutexes, semaphores e outros, a Solaris apresenta algumas opções interessantes. Dentre essas opções estão Scheduler Activation, que permite a thread comunicar com o escalonados do sistema operacional; Rechoose Interval, que permite transferir as threads de um core para outro; Memory Placement Optimization, que permite que garantir que os dados relacionados a uma thread estarão no core mais próximo que ela se encontra.
As threads para esse tipo de arquitetura são as mesmas com quais todos estão acostumados a programar nos processadores single core. Assim, qualquer aplicação desenvolvida em pthreads, por exemplo, para um single core funciona em um processador CMT. As aplicações típicas, como já foi dito, para esse tipo de arquitetura são as aplicações de banco de dados, servidores web e roteadores.
Até o momento, somente o sistema operacional solaris suporta a arquitetura do Niagara. No entanto, existem esforços para o Ubuntu suportar o Niagara.

### Futuro

A Sun já iniciou o desenvolvimento do Niagara 3. Ainda não existem muitas informações sobre o projeto, mas a tendência de aumentar o número de cores, threads e largura de banda continuará. Outro projeto da família do Sparc que está desenvolvimento é o Rock.. A ideia é que o Rock seja escalável e utilizado em servidores para aplicalções high-end. A primeira versão dele possui 16 cores e duas threads por core.

### Sun ULTRASparc T1 e T2

Os Sun ULTRASparc T1 e T2 são codinomes comerciais para a arquitetura do Niagara. A arquitetura Niagara somente é comercializada através deles, em um pacote completo, que contém o gabinete da Sun, disco, placa mãe e memória principal.
As especificações deles podem ser encontradas em:
* ULTRASparc T1
* ULTRASparc T2

## Referências

* Niagara: A 32-Way Multithreaded Sparc Processor.
* A Power-Efficient High-Throughput 32-Thread SPARC Processor.
* The UltraSPARC T1 Processor - High Bandwidth For Throughput Computing.
* UltraSPARC Architecture 2005.