KGROUND-COLOR: #ffff00">Esse artigo faz parte da revista Java Magazine edição 53. Clique aqui para ler todos os artigos desta edição.

; FONT-FAMILY: Verdana">Perspectivas para um Mundo Paralelo

O que pode acontecer no mundo da programação na era das arquiteturas massiçamente paralelas

As próximas revoluções da Programação Concorrente: mudanças prometidas para o Java SE 7, técnicas de programação funcional e Memória Transacional

 

Há quanto tempo você programa em Java? Não vale dizer mais de 11 anos, pois o JDK 1.0 só foi lançado em janeiro de 1996. E mesmo esta data é “generosa”, pois o JDK 1.0 (cá entre nós) não passava de um brinquedinho; um beta disfarçado. O Java começaria a ser levado a sério a partir de 1997 (JDK 1.1.x), e começaria a exibir desempenho e funcionalidades competitivas com linguagens estabelecidas como C/C++ a partir de 2000 com o Java 2. O mesmo pode ser dito das plataformas-filhas, J2EE e J2ME, que nasceram há quase oito anos, mas também levaram alguns anos mais para conquistar a maturidade – e o lugar ao sol – que possuem hoje.

É bom lembrar como o Java ainda é novo. Quando me formei em Computação – no mesmo ano em que o Java era lançado – linguagens como C++ e Visual Basic dominavam, e poucos diriam que este domínio não iria durar décadas mais. Sim, muita gente conhecia linguagens “superiores” em algum aspecto, como Smalltalk ou Eiffel, mas parecia impossível quebrar diversos obstáculos:

·       A superioridade de desempenho de linguagens de baixo nível, como C e C++, quando comparadas a linguagens mais “puras” como Smalltak;

·       A facilidade de uso de ambientes de desenvolvimento rápido, como VB e Delphi;

·       O enorme investimento feito por fornecedores de plataformas e ferramentas, como Microsoft, Borland etc., bem como seus clientes, sem falar no treinamento de muitos desenvolvedores;

·       A dificuldade (então considerada alta) de novos paradigmas como Orientação a Objetos (embora a linguagem C++ fosse popular, poucos programadores a usavam de forma realmente OO);

·       Resistência a mudanças em geral. (Isso era ainda pior antes da crise do Y2K, que “chacoalhou” mesmo as corporações mais conservadoras para repensar suas estratégias de TI.)

 

Mas apesar de tudo isso, o mundo do desenvolvimento virou de pernas para o ar, e não foi só na adoção do Java: também em outras frentes como o surgimento da Internet e o crescimento do movimento de software livre, ambos com grande impacto sobre todo o cenário do desenvolvimento de software.

Nada mais natural então do que imaginar quais serão as próximas mudanças. Por exemplo, existirá uma edição 173 da Java Magazine, daqui a dez anos? Certamente a Sun, o JCP e outros guardiões do Java ainda estarão por aí, promovendo o Java SE 12. Mas isso não será suficiente, se for um “novo COBOL” relegado à manutenção de aplicações empoeiradas. Será que o Java continuará tendo a importância que tem hoje por muito mais tempo? E se continuar firme e forte, o Java de 2017 será reconhecível comparando-se com o que temos hoje?

 

A necessidade de novas linguagens

Novas linguagens de programação não são inventadas somente por esporte. Existem duas forças principais que motivam a sua criação e modificação.

 

Evolução da tecnologia

Idéias inovadoras, inicialmente consideradas ineficientes, podem com o tempo ser utilizadas de forma mais ampla, devido à evolução das técnicas de implementação. Um bom exemplo é a própria Orientação a Objetos, que surgiu nos anos 70, mas por muito tempo foi sinônimo de código lento e inchado. Somente após duas décadas de pesquisas e aperfeiçoamentos em compiladores, linguagens OO puderam se livrar deste estigma, sendo hoje usadas até em software de tempo real.

Citando exemplos mais próximos a nós, características do Java como gerenciamento de memória automático (com Garbage Collection) e bytecode portável (com execução em uma VM) seguiram o mesmo caminho – da obscuridade de linguagens acadêmicas ao domínio do mercado, com as plataformas Java e .NET. Neste segundo caso o caminho foi ainda mais longo, pois são tecnologias anteriores mesmo à Orientação a Objetos. Ambas datam do início dos anos 60, mas atingiram maturidade tecnológica apenas há poucos anos – e tendo a JVM como principal protagonista da “arrancada final”.

 

Pressão das demandas do mercado

O mercado sempre quer ferramentas que atendam às necessidades de projetos cada vez maiores e mais complexos, e com tempo e custo de desenvolvimento menores. Este fator não é algo especial ao desenvolvimento de software; é um lugar-comum na tecnologia e na indústria em geral, embora seja mais acentuado no nosso campo profissional.

Outros fatores complicam a vida dos designers de linguagens de programação. Por exemplo, o perfil profissional dos programadores é cada vez mais diferenciado. Se nos anos 50 quase 100% dos desenvolvedores eram engenheiros ou matemáticos, hoje há muita variedade de cursos e profissões que envolvem programação, muitas especializações; vários paradigmas, ideologias e metodologias competidoras. Até mesmo entre os que usam a mesma ferramenta primária, como a linguagem Java, existe grande variedade de gostos e estilos – haja vista o enorme número de soluções para certos problemas, como frameworks web. E num extremo, temos cada vez mais programação sendo feita por não-profissionais sem treinamento formal em computação: economistas e administradores criando macros complexas com o Excel, designers programando scripts do Flash ou JavaScript, analistas de negócio modelando detalhes do comportamento de sistemas com UML, e assim por diante.

 

As próximas forças

Vamos tentar identificar os fatores – “forças de design” – que irão motivar as próximas mudanças na área de programação. Existem algumas forças muito importantes e fáceis de identificar.

 

Escalabilidade por paralelismo

Chegamos ao fim a “era dos Gigahertz”, fato que já comentei nesta coluna e já passou de revelação a clichê. A indústria de semicondutores atingiu barreiras físicas que inviabilizam a escalada da velocidade das CPUs, pelo menos no ritmo com que nos havíamos acostumado nas décadas de 80 e 90.

Aliás, uma curiosidade que me aconteceu este ano foi reler “O Universo numa Casca de Noz”, um livro de 2002 do físico Stephen Hawking, e encontrar uma figura falando da evolução das CPUs – projetando 10 GHz para... 2007!1. Este ano já está acabando e ainda não temos nem metade disso.

Em compensação, temos mais CPUs; para ser exato, mais cores (núcleos) nas novas arquiteturas multi-core. No momento em que escrevo este artigo, já existem produtos de massa (computadores pessoais e até consoles de jogos) com até quatro núcleos. E isso é só o começo; logo teremos centenas ou mais núcleos por CPU.

Na vanguarda atual, a Sun já está com o chip “Rock” de 16 núcleos em produção experimental (à venda em 2008), e na Intel o projeto de pesquisa TeraScale já demonstra 80 núcelos numa única CPU (esse projeto, mais verde, ainda levará alguns anos para resultar em produtos de mercado). Mesmo hoje, já temos o chip Vega2 da Azul Systems que tem 48 núcleos, porém este é um chip bem mais limitado que os outros mencionados, sendo aproxidamente equivalente2 a um Niagara 2 da Sun (que tem 8 núcleos físicos, mas 32 threads via hyper-threading).



1 Esta projeção era cortesia da Intel, somente citada por Hawking.

 

2 O Vega2 só é capaz do executar código Java (o que para nós é OK!), e com um desempenho médio relativamente baixo por core, devido ao compartilhamento de uma quantidade pequena de cache e de unidades de ponto flutuante.

 

Produtividade

Do item produtividade já falamos um pouco: o mercado sempre nos leva a produzir mais cada vez mais rápido. Linguagens de programação que prometem ser mais fáceis estão em alta. Na verdade sempre estiveram. O único problema é que as “linguagens milagrosas” se sucedem como a moda da alta costura: todo ano há uma novidade maravilhosa, e a moda do ano passado vai pro brexó. Mas, apesar dos tropeços existem avanços, com a progressiva liberação do desenvolvedor de responsabilidades como escrever código repetitivo para diversas tarefas, como a persistência.

Atualmente, esta tendência responde pelo interesse em linguagens dinamicamente tipadas como Ruby, Groovy, Python e outras, que possuem uma sintaxe mais enxuta e ciclo de desenvolvimento um pouco mais ágil que o do Java. A plataforma Java tem se adaptado a esta tendência com uma progressiva abertura da JVM para outras linguagens, com a JSR-223 (javax.script) no Java SE 6, Groovy, JRuby; nova instrução invokedynamic no Java SE 7 e outros.

 

Qualidade

A necessidade de construir software cada vez mais correto dispensa justificativas. No entanto, a impressão geral é que a contagem de bugs só faz aumentar. Os usuários só não nos condenam porque a gravidade média dos bugs tem diminuído. Isso, pelo menos, foi um resultado bastante concreto da evolução das linguagens, compiladores, sistemas operacionais, servidores de aplicações e outras ferramentas ou componentes de infra-estrutura. Esses softwares hoje previnem ou tratam de forma automática a maioria dos problemas que antigamente causariam uma falha catastrófica (crash ou corrupção de dados).

Em Java, por exemplo, toda uma classe de erros graves de manipulação de memória – que ainda infestam aplicações nativas – é impossível, devido ao modelo de memória gerenciado. Um stack trace de NullPointerException é um substituto desagradável para o correto funcionamento de uma operação; mas um crash da aplicação é muito pior. Não há como negar que houve progresso.

Por outro lado, as aplicações têm crescido em tamanho e em complexidade, numa velocidade maior do que a capacidade dessas tecnologias de reduzir a contagem total de bugs. Muitos reclamam de softwares modernos que têm dezenas de milhares de bugs conhecidos. É fácil comparar isso com os “bons tempos” de aplicações que, se tivessem uma dúzia de bugs, achava-se muito. Mas para fazer justiça, devemos lembrar que as aplicações de 20 anos atrás cabiam folgadas em disquetes de 360 Kb, e as atuais já começam a encher DVDs de 4,7Gb (13 mil vezes mais).

O dilema é elaborar linguagens de programação cada vez mais robustas: que auxiliem a escrever código de qualidade – e de preferência com pouco esforço. Isso porque com bastante esforço, há ferramentas capazes disso há muito tempo; desde linguagens “duronas” como Ada e Eiffel até metodologias ultra-formais como Z ou Redes de Petri. Mas essas ferramentas permaneceram em relativa obscuridade, pois seu uso é difícil e caro. Só são viáveis em projetos cuja extrema necessidade de corretude justifique custos e tempos de desenvolvimento até dez vezes maiores que o normal; por exemplo, nas indústrias aeronáutica, médica ou militar.

Para os projetos mais feijão-com-arroz que 99% de nós somos pagos para fazer, esqueça a idéia de usar ferramentas altamente formais; isso jamais irá pegar. Por outro lado, existe a possibilidade de ferramentas mais acessíveis evoluírem de forma a incorporar alguns aspectos mais robustos, numa versão “light”, seja através de automatização, seja com sintaxes facilitadas ou outros meios.

Nesta área não vejo grandes tendências ou revoluções a caminho. No Java SE 7, teremos a JSR-305 (Anotações para Defeitos de Software) que irá especificar anotações como @NotNull para definir que um parâmetro de um método não pode ser null, entre outras coisas do tipo. Isso permite que diversos tipos de erro, que hoje só são detectados em tempo de execução (resultando numa exceção), possam ser detectados no momento da compilação.

 

Paralelismo

O maior desafio é o do paralelismo. Este problema é extremamente importante e também urgente. E como já estamos vivendo o início da “era multi-core”, toda a indústria já está se movimentando para responder aos desafios nesta área. Por isso, vamos nos concentrar no restante do artigo na questão do paralelismo.

O problema da tecnologia multi-core é que a maioria dos programadores não conseguiria programar código concorrente de qualidade, nem mesmo com apenas dois ou três threads... quanto mais com as centenas ou milhares, que logo serão exigidos para explorar o potencial das novas arquiteturas.

Não estou dizendo, claro, que a maioria dos programadores não tem competência suficiente. O problema é que a Programação Concorrente é incrivelmente difícil. Se multiplicarmos as duas dimensões desse problema – um grande número de threads e uma grande quantidade de dados (objetos, variáveis) compartilhados por vários desses threads – o resultado para qualquer aplicação de médio a grande porte tende a ser um emaranhado infernal de dependências.

Garantir a ausência de deadlocks, livelocks e race conditions torna-se tarefa heróica. E quando você conseguir isso, não pode se dar por satisfeito só porque não tem bugs de concorrência. Seu código só será realmente bom se for extremamente escalável... Isso só será verdade se você fizer poucos locks e por pouco tempo. A estratégia “espalhar synchronized por toda parte” não serve neste caso.

Quer um exemplo ilustrado da encrenca? Então carregue seu IDE Eclipse 3.3 com um workspace bem populado, configure a view Progress ativando Preferences>Show sleeping and system operations e dispare algumas operações lentas, como um checkout e um rebuild do workspace. Agora veja a view Progress.

Capturei o resultado dessa experiência na Figura 1, onde podemos observar alguns fatos: o número de tarefas, a possibilidade de algumas executarem em paralelo, dependências entre tarefas e recursos (como projetos ou classes), “daemons” de execução periódica e dependências dirigidas por eventos (por exemplo, uma compilação gera eventos de alteração de recursos – no caso os ...

Quer ler esse conteúdo completo? Tenha acesso completo