Domingo, Novembro 29, 2009

C++ em múltiplas configurações

Parte dos meus esforços em entender o ambiente de programação C++ é buscar os padrões de uso do pré-processador, algo que eu nunca encontrei catalogado sistematicamente.

Ao meu ver, existem dois grandes grupos de usuários do pré-processador: programadores que usam macros como formas de reusar código totalmente inline, e programadores que usam macros para variar a definição de um programa C++ de acordo com variáveis de configuração.

A comunidade do C++ acredita que a necessidade de usar macros hoje praticamente não existe; essas pessoas deveriam ser mais específicas e dizer que a necessidade de usar macros para reusar código totalmente inline já não existe, porque compiladores otimizadores e templates de função são capazes de fazer isso.

(Eu chamo reusar código totalmente inline a uma forma de reuso que insere código diretamente no chamador, sem utilizar o mecanismo de chamada de subrotina, que envolve empilhamento, desempilhamento e saltos.)

Porém, não há solução sintática para o problema das configurações -- ao menos, não há solução bem conhecida. É interessante ler sobre a história do autoconf.

Quinta-feira, Novembro 26, 2009

Otimização nas Sombras do C++

Eu não encontro pessoalmente esse tipo de coisa com muita frequência atualmente, mas foi tempo em que volta e meia me aparecia código-fonte muito esperto, ou críticas muito espertas sobre o meu código-fonte, com relação a quão "ótimo" ele é.

Em C, essas espertezas eram invariavelmente transformações de uma notação legível e imediatamente representativa do algoritmo lógico por uma notação ilegível, equivalente mas que executava mais rápido.

Truques de aritmética de ponteiros para escrever mais rápido na memória, truques horrendos com while e switch sem break pra desenrolar loops -- vários truques cujo objetivo é escrever o mesmo código gerando um programa melhor.

De certa forma, os truques dessa época eram motivados pelos compiladores dessa época, máquinas de transformação burra de C para assembler. Com o objetivo de gerar o melhor assembler, programadores produziam pior C -- mais rápido, sim, mas menos legível e de mais difícil manutenção.

Com o passar do tempo, os compiladores se tornaram otimizadores, capazes de inlining, remoção de código morto, desenrolamento de loops, progragação de valores constantes etc.

As máquinas, por sua vez, introduziram pipelines para otimizar o trabalho de processador mais e mais rápido que a memória, capazes de execução computações intermediárias simultaneamente a recuperação de dados da memória, especulação sobre valores futuros para adiantar computações etc.

Compiladores cientes do pipeline passaram a otimizar ainda mais, ordenando e reordenando instruções de modo a obter o melhor desempenho do otimizador alvo.

Quem tem à mão um excelente compilador otimizador não precisa mais se preocupar em produzir código esperto. O compilador quase invariavelmente é melhor do que o programador humano. Ele também é a ferramenta adequada para a esperteza -- porque projetos críticos frequentemente transformam um código-fonte em programas para diversas máquinas, onde as peculiaridades variam muito.

Mas então, um dia, surgiram as máquinas multi-processadas. A seguinte citação resume a tragédia da programação para tais máquinas:

Yeah, I know. "Multithreading is hard" is a cliché, and it bugs me, because it is not some truism describing a fundamental property of nature, but it’s something we did. We made multithreading hard because we optimized so heavily for the single threaded case.

O problema fundamental de um programa multithreaded em uma máquina onde os processadores compartilham a memória é o de garantir que um certo objeto lógico -- uma sequência de bytes na memória -- não será escrito e lido de forma incosistente por múltiplos processadores -- porque um está escrevendo pela metade quando outro está lendo, etc.

Por mais que um programador faça esforço para ordenar adequadamente as instruções em um programa -- de modo que qualquer ordem de leitura possível faça algum sentido -- o fato é que o otimizador do compilador, e o otimizador do pipeline, fará mágicas com a presença e a ordem das instruções, violando a expectativa do programador.

Desse modo, os truques espertos da atualidade não são truques para otimizar o assembler e sim truques para evitar inconsistência no acesso concorrente à memória.

As pessoas legais agora falam sobre atomics e memory barriers.

O que há de curioso aqui é que esse problema espirra em certas direções mesmo onde não há múltiplos threads. Ao programar uma máquina com suporte a memory mapped I/O o mesmo problema de consistência ocorre sobre um endereço memory mapped -- reordenar leituras e escritas ali resulta no caos.

Tendo chegado até aqui, releia seu livro favorito sobre C++ sobre o significado da palavra-chave volatile.

Terça-feira, Novembro 24, 2009

Importei meus blogs antigos!

Meu primeiro post no Blogger foi em 2001!!!

Segunda-feira, Novembro 23, 2009

Mais sombras no Ambiente de Programação C++

Estou lendo a ementa do curso de pós-graduação em Análise e Projeto de Sistemas da PUC-Rio, onde ocorre o seguinte:

"Laboratório de Linguagens de Programação

Objetivo – Capacitar o aluno a distinguir os principais componentes de linguagens de programação não convencionais tais como: programação orientada a objetos, programação orientada a eventos e programação para Internet/intranet."

A presença de "programação orientada a eventos" ali é interessante; a ementa prossegue em explicar que neste módulo o aluno aprenderá a programar Applets Java, que são orientados a eventos.

O que se chama "orientação a eventos" ali é uma técnica de projeto de aplicativos úteis plugados em algum tipo de framework de apoio, que cuida de enviar os tais eventos para o aplicativo.

Há um sub-grupo dos programadores de C++ que dirá a você que "não programa interfaces de usuário". Essa é uma afirmação interessante porque, compreendido com rigor máximo, significa que os programas produzidos por essa pessoa produzem resultados totalmente invisíveis.

Todo programa de computador possui uma interface de usuário -- senão, o usuário nunca veria seu resultado. Programadores C++ são criados desde o berço para produzir programas cuja interface com o usuário é o terminal -- um dispositivo capaz de apresentar um mapa de 80x24 caracteres.

O que há de interessante nisso é que existe um mecanismo subjacente a esse terminal que suporta a parafernália usada para pôr ali os tais caracteres -- std::cout, printf etc. Esse mecanismo é inicializado, mantido e finalizado pela implementação do C++ de forma oculta ao usuário.

Existem ambientes peculiares onde o C++ não inicializa nada disso -- ou inicializa essas coisas de forma inesperada. Os compiladores da Microsoft oferecem um parâmetro de chamada ao compilador para selecionar o "subsistema" -- CONSOLE ou WINDOWS. Aplicativos compilados para o subsistema WINDOWS não fazem coisas úteis quando o programa chama printf.

No projeto onde estou alocado agora, o sistema implantado no dispositivo-alvo é altamente customizado, e a biblioteca padrão C++ apresenta esquisitices parecidas. Não existe um "terminal" e chamar printf às vezes não faz nada, às vezes bloquea o sistema eternamente em um signal handler.

Bibliotecas como a Qt e a Glib oferecem uma terceira via, que talvez pudesse ser compreendida como um terceiro subsistema, o EVENT_LOOP. Esse subsistema possui sua própria API e faz tudo pela aplicação, que apenas inicia operações no sistema e senta esperando por notificações sobre o resultado. Assim são os Applets Java.

Tudo isso sugere que o ambiente de execução do C++ executa mais que apenas o código da aplicação, e de fato existem atividades que ocorrem antes de main iniciar e depois de main finalizar. O programador atento procurará pelo código-fonte ou executável do CRT no seu sistema -- o C Run-Time.

Segunda-feira, Novembro 16, 2009

A Habilidade da Lentidão

Na outra semana estivemos no mo kwoon com um colega novo, aprendendo os primeiros mistérios do kung fu.

Este novo colega, julguei, se enquadrou facilmente no grupo dos afobados; sempre seu discurso o convidava a dar "exemplos" do golpeamentos, e cada um deles mais e mais próximo do corpo da pessoa com quem estava conversando. Assim é a ansiedade de quem conhece a arte marcial através de romances e obras audio-visuais.

Em um determinado momento, discutindo os princípios do ving tsun, este novo colega levantou uma questão sobre a eficácia de um posicionamento defensivo: por mais que o outro esteja posicionado adequadamente, eu posso desarmá-lo sendo bem rápido, não posso?

A objeção padrão a esse tipo de hipótese é simples: e se o outro for tão rápido quanto você? Há diversos caminhos interessantes de discussão que iniciam com essa resposta.

Nesse dia, porém, me sentido inspirado para falar sobre a minha experiência pessoal. Isso acontece às vezes durante a aula e eu me dou a liberdade de falar porque, sendo um igual, me parece inspirador para os mais novos ouvir sobre as minhas dificuldades e realizações.

Elaborando o meu relato, eis o que eu tenho a dizer sobre o assunto.

É claro o poder de agir em alta velocidade é útil e desejável. Mas a percepção de que este poder é o grande poder, ou mesmo o verdadeiro poder, é enganosa. Em particular, os modos de viver e produzir em nossa sociedade constróem um excesso de valor sobre este poder em particular; além do grande apelo dramático de certos filmes.

Quando eu comecei a treinar artes marciais logo se tornou evidente uma quantidade enorme de tensão no meu corpo. Esta tensão habitava, porém, um corpo vazio do impulso de agir -- o período de treinamento mais particularmente focalizado em "golpear" parecia quase totalmente inútil.

Mas, como inevitável, em um determinado momento a tensão encontrou seu caminho e se tornou então evidente a ansiedade por agir que estava contendo. É ainda verdade que passo boa parte do meu tempo resolvendo uma ansiedade por agir. Essa ansiedade não resolvida se resolve em impulsos de ação -- de súbito, uma vontade de ler, de súbito, uma vontade de falar, de súbito, uma vontade de produzir, de súbito, uma vontade de não fazer nada.

Se você está convencido de que esse modo de ser é razoável, então o poder de agir e completar ações em alta velocidade parecerá o ideal da vida. Afinal, se um determinado surto de ação dura pouco, é preciso fazer muito com esse pouco. Assim, quem vive de ansiedades em ansiedades realiza pouco, porque não se pode fazer muito em pouco tempo.

Estrategicamente, podemos enxergar nessa pessoa a empresa que se vê presa na teia do time to market. Essa empresa está sempre se "reajustando" ao "mercado", mudando de rumo de acordo com suas "tendências", se "alinhando" com cliente. A cada momento suas prioridares são reavaliadas e alguma outra coisa se torna prioridade máxima.

Esta empresa nunca realizará as atividades com prioridade não-máxima, nunca realizará os projetos de médio a longo prazo. Ela está cheia da ansiedade por obter os resultados cuja possibilidade só passou a enxergar hoje. Está sempre correndo atrás de algum prejuízo.

Ela não compreende que a possibilidade que se realiza hoje está amadurecendo desde a tempo passado. Se ela vê hoje as condições de formação da possibilidade futura, poderá agir para obter os resultados do futuro. O indivíduo que se interessa por uma atividade hoje, se trabalhar com paciência, poderá realizar as grandes coisas que exigem muitos dias de atenção.

Assim, enquanto é sempre possível resolver as crises e se adequar às mutações através da habilidade da rapidez, é também sempre possível resolver as crises e se adequar às mutações através da habilidade da lentidão. Atuar com lentidão exige -- permite! -- atuar com antecipação. Para atuar com antecipação, é preciso ser lento; para atuar com antecipação, é preciso acompanhar com cuidado as correntes das mutações; não se precipitar.

Não é fácil atuar com lentidão, o que pode não parecer intuitivo. Quando o indivíduo experimenta exercícios simples, como andar a um passo com metade da velocidade do seu passo normal, ele compreende rapidamente que a ansiedade é uma propriedade fundamental do seu corpo -- não obstante a quantidade que ocorre em um e em outro.

Mesmo a alta velocidade corporal exige o lento acomodamento da mente à situação; quando a mente é um turbilhão, não é possível agir em alta velocidade sob controle; citando um belo anime, quando um guerreiro usa suas armas fora de controle, ele pode cortar algo que não deseja cortar, ou mesmo cortar aquilo que ele deve defender.

Se tornou, então, foco do meu treinamento em artes marciais a habilidade de agir lentamente, com eficiência. Pelas razões expostas acima, agir lentamente com eficiência não é uma contradição. É um objetivo necessário a quem acumula tensão e ansiedade; provavelmente seria diferente para disposições pessoais diferentes.

Sexta-feira, Novembro 13, 2009

As sombras do Ambiente de Programação C++

Estou realmente escrevendo sobre este assunto! É claro que, até reunir material suficiente para um livro maneiro, vai demorar muito tempo. Até lá espero encontrar alguma publicação legal que aceite artigos.

Minha motivação inicial para entender os mistérios do ambiente de programação C++ foi estudar a ABI do C++, que seria necessário implementar no sistema operacional C++.

Existem outros aspectos interessantes desse ambiente, porém, que são pouco focalizados pelas obras educativas sobre a linguagem.

Um deles é o relacionamento entre o mundo restrito da norma ISO C e o mundo maior do sistema. C fala a você sobre unidades de tradução e estabelece regras mínimas sobre como múltiplas unidades de tradução podem compor um programa válido. Ao redor da linguagem, porém, existe o problema de processar cada uma dessas unidades e produzir de fato um negócio único equivalente a este programa. Existirão então diversos blocos intermediários, os "objetos compilados", e existirá um componente ligador. A norma C não descreve este ligador, apenas impõe seus requisitos.

Outro é o modo como os mecanismo de linguagem e do sistema são usados na prática como mecanismos de abstração, e como os projetos de engenharia fazem uso desses mecanismos para lidar com problemas reais de configuração. O ambiente como um todo provê diversas maneiras de abstrair certos trechos de código; desde a seleção de arquivos a compilar, onde um arquivo pode conter a listagem para o sistema A enquanto outro contém a listagem para o sistema B; até a composição de bibliotecas dinâmicas no momento da carga na memória, onde o programa usa a versão A da biblioteca no sistema A, e a versão B da biblioteca no sistema B.

Para lidar com toda essa parafernália sistêmica ao redor do C são imprescindíveis ferramentas que operam fora da linguagem, dirigindo todas as ferramentas de forma conveniente. O projeto de middleware de televisão digital da TQTVD tem, por exemplo, aproximadamente mil unidades de tradução individuais a processar. Certamente o programador não deve chamar as ferramentas uma vez para cada um desses arquivos, sob pena de tornar o dia de trabalho um desperdício de horas absurdo. Existem portanto ferramentas para apoiar essa tarefa que, apesar de não fazer parte da definição da linguagem ou do compilador, são absolutamente imprescindíveis para se trabalhar com ela.

Tudo isso implica que a expertise no desenvolvimento de sistemas em C++ envolve diversas outras habilidades e técnicas individuais, fora aquelas envolvendo a expressão de programas em C++. Essas outras habilidades e técnicas se tornam tão mais importantes quanto o projeto pressione o programador para fora da abstração de um sistema para o mundo concreto das diversas máquinas.

Sob outro ângulo, posso comparar as listas de componentes teórica e prática que compõe o pipeline de transformação de código-fonte. Teoricamente, o pipeline C é: pré-processador, compilador, ligador ou arquivador. Na prática do projeto onde estou envolvido é: configurador de build, ferramenta de build, compilador.

Uma lição que estou tirando de tudo isso é que os melhores pensamentos sobre a linguagem de programação ideal nunca resultariam em um excelente sistema construído se essa linguagem não for implementada em ferramentas adequadas, que suportem um ciclo de desenvolvimento extremamente conveniente. O esquema de compilação separada do C oferece bons mecanismos de abstração, sem dúvida, mas torna o tempo de compilação uma infinitude de "começa e termina" processos.

Em contra-partida, opiniões sobre características de linguagem de programação do tipo "viagem na maionese" ou "impossível" parecem refletir o fato de que, até então, ninguém soube produzir uma ferramenta que a implementasse. O mecanismo de "export" do C++ por exemplo foi considerado um fracasso; agora que as técnicas de LTO e serialização de ASTs de programas estão se tornando mais difundidas, talvez com mais cinco ou dez anos "export" seja trivial.

Quinta-feira, Novembro 12, 2009

MIT OpenCourseWare

Estou cursando Introdução à Ciência da Computação no MIT todos os dias de manhã, indo para o trabalho!

Para quem não sabe, em 2002 o MIT lançou o projeto OpenCourseWare para publicar material didático dos seus cursos online sob uma licença do Creative Commons.

Alguns cursos do MIT agora têm vídeos das palestras disponíveis como parte do OCW. Dá pra assistir perfeitamente no meu telefone de manhã.

Segunda-feira, Novembro 09, 2009

Baixar MP3 e bandalhas no trânsito

Hoje de manhã presenciei uma peripécia no trânsito que finalmente me iluminou sobre a razão porque eu parei de baixar mp3 na Internet.

Estávamos seguindo pelo aterro até a Presidente Antônio Carlos e havia um trânsito razoável. Parados do lado daquele monumento de guerra, antes do MAM, vimos um e outro carro passando pela pista interna que tem ali na frente do monumento -- uma pista para um carro que começava um pouco antes e terminava a uns cem metros de onde estávamos.

Discutimos um pouco a validade daquela peripécia, porque o sujeito não ia ganhar muito tempo com aquilo, e eu pensei que não valia a pena ganhar tão pouco tempo com um truque daqueles.

Depois, pensei por que não valia a pena; afinal, ganharia-se um tempo zero, certo? Desconsiderando o problema do primeiro eu da classe média.

Me ocorreu então que tipo de pressa levaria uma pessoa a se desviar da rota normal para tentar economizar tempo. Pensei: espero que esse sujeito esteja economizando um tempo útil, porque de outro modo ele está se aborrecendo à toa. Então me ocorreu um processo de ansiedade que levaria um motorista a procurar rotas "espertas" para chegar mais cedo onde quer que seja.

Então eu entendi porque eu mesmo nunca faria aquilo: porque um processo de ansiedade tem em geral dois futuros, a satisfação ou a supressão; e satisfazer a uma ansiedade é o mesmo que alimentá-la.

Esta é a razão porque eu não uso mais programas de baixar mp3, nem tenho um instalado no meu sistema. Quando você tem gosto por música e descobre os navegadores de mp3 surge uma ansiedade -- a ansiedade de ter tudo. O mundo se transforma de um local onde você não pode ouvir tudo que existe, porque precisa transformar recursos em CDs, para um mundo onde você pode ter tudo, em troca de algum tempo de pesquisa e espera. Os baixadores de mp3 são pessoas que se vangloriam por pegar álbuns inteiros, como se baixar músicas individuais fosse indigno e revelasse a condição de ouve-por-modinha ao invés de um apreciador verdadeiro. Os programas passam 24h por dia ligados obtendo as coletâneas mais obscuras encontráveis, entupindo o HD, e o indivíduo faz ALT+TAB frequentemente para observar as barras de progressão de download.

Quando eu percebi essa ansiedade, cortei o hábito.

Terça-feira, Novembro 03, 2009

Java vs. C#, uma perspectiva estranha

Você pode achar o que quiser na disputa entre Java e C#, mas onde na documentação da Microsoft você vai encontrar uma afirmação tão direta, sincera e esclarecedora?

"Prior to Java 2 Standard Edition, JDK 1.4, the AWT focus subsystem was inadequate."

http://java.sun.com/javase/6/docs/api/java/awt/doc-files/FocusSpec.html