Assembly faz bem pra você, mas com moderação…

Muitos acham que assembly é algo complicado e que não deve ser estudado por ser muito “baixo nível”. Mostrei pra você, neste post, que um pouco de conhecimento de assembly é essencial para o bom entendimento do que o seu compilador faz, por baixo dos panos. Acredito que, se você quer trabalhar com C/C++, é essencial para entender o que são e como funcionam os ponteiros, por exemplo.

O fato é que os compiladores C/C++ atuais (e só eles) geram código bem otimizado, usando um sub-conjunto (bem escolhido) de instruções disponíveis no seu processador. Desenvolver rotinas inteiras em assembly, neste sentido, pode ser bastante contra-produtivo. Nisso, os assembly-haters têm razão, parcialmente.

Outro fato é que apenas uma rotina em assembly bem construída pode alcançar o mais alto padrão de performance para o seu processador. Como Michael Abrash disse, em seu livro The Art of Code Optimization: “O melhor otimizador está entre suas orelhas!”. Esse fato é bem fácil de explicar, com uma historinha verídica (e verossímil! hehe) que aconteceu comigo, quando era professor:

Na época eu ministrava aulas das disciplinas de eletrônica, eletrônica digital e microprocessadores. No entanto, de vez em quando me pediam para ministrar cursos básicos de Windows e coisas do gênero. Numa dessas vezes estava eu explicando…

— … Estão vendo essa “setinha” na tela. Ela é chamada “ponteiro”, porque você, usando o mouse, “aponta” para um item, clica e ele é selecionado.

Neste momento fui interrompido por um aluno querendo maiores explicações…

— Professor, eu estou apontando para esse “desenho” na tela (um ícone) e, quando “clico” o botão do mouse, nada acontece!

Cheguei perto do sujeito e observei que ele estava, de fato, apontando para o ícone. Mas não estava com o ponteiro sobre ele… Ao ver isso, é claro, caí na gargalhada… não tanto pela interpretação do aluno, mas pela minha ineficiência em explicar que “apontar” era só uma metáfora.

Qualquer novato, usuário, sabe que as metáforas que usamos para designar coisas ou ações, no ramo da informática, não devem ser levadas ao pé-da-letra. O que eu presenciei na época foi um erro de interpretação (causado por mim mesmo). E é ai que eu volto para a discussão original sobre assembly…

O que um compilador faz? Ele traduz o conteúdo de um arquivo texto, expresso numa linguagem que só ele entende e a transforma em outra linguagem que o processador entende. Linguagens como C e C++ são aritméticas, ou seja, são baseadas na matemática. Usamos a matemática para criar algoritmos de forma que essas instruções possam ser perfeitamente traduzidas pelo compilador. Não é possível, ainda, criar programas ao estilo de Star Trek: “Computador, faça isso…”.

Já que o que o compilador faz é uma tradução, sempre existe a possibilidade de erros de interpretação. Às vezes você pretende que o compilador faça uma coisa e ele faz outra e isso tem um nome baseado em outra metáfora: bugs.

Pessoas que afirmam categoricamente que compiladores de linguagens feitas para ambientes gerenciáveis (managed), como Java e C#, geram código tão bom quanto C/C++ e assembly, não entendem a impossibilidade lógica dessa afirmação… Java e C# fazem a tradução de seu código em duas etapas: Primeiro traduzem o seu código para um código intermediário (bytecode, no Java, e intermediate code, no .NET). Depois, o ambiente virtualizado (a virtual machine) faz uma tradução desse código intermediário para o código nativo (não estou exagerando… A Java Virtual Machine faz isso através do que é chamado de HotSpot e o .NET foi concebido assim!).

Ora, se na primeira etapa há possibilidade de erros de interpretação, na segunda também há. E, para piorar, a segunda etapa deve gerar código genérico o suficiente (do ponto de vista do processador) para que atenda a todas as necessidades do código intermediário. Isto é, o código final segue algumas regras básicas e pré-estabelecidas de otimização, que estão longe das mãos do desenvolvedor original (você!).

Já que, acredito, ficou claro que somente assembly permite que você faça o tunning de performance de sua eplicação, deixe-me reafirmar que compiladores como o GCC fazem esse trabalho muito bem. E você pode verificar isso por si mesmo: Basta usar a chave de compilação -S para ver o que o compilador fez (ou seja, gerar o código em assembly e ver o que aconteceu).

Hoje, acredito que o assembly deve ser primariamente usado para entender o que o compilador fez, no caso de você precisar saber para onde todos aqueles ciclos de máquina estão indo numa rotina que deveria ser realmente rápida! Dar uma mãozinha ao compilador pode ser interessante, de tempos em tempos, mas desde o início dos anos 90 (pelo menos), fazer isso é cada vez mais raro.

Agora, se você é desenvolvedor de aplicações para micro-controladores (Arduino, por exemplo), pode achar que o assembly está mais vivo do que nunca. Eu não acho… Os compiladores C para esses ambientes também geram códigos muito bons. Daí, as regras para o uso de assembly, mesmo nesses ambientes, continuam as mesmas: Verificar o que o compilador fez e, raramente, dar uma mão ao compilador.

Outra coisa: O argumento da portabilidade, para não incentivar o uso do assembly, nunca me convenceu. O motivo é que processadores diferentes têm arquiteturas diferentes. Mesmo se você usar apenas os recursos padronizados do seu compilador ou da linguagem de sua escolha, vai topar com diferenças de arquitetura que te farão criar código customizado. Por exemplo: Nos processadores MIPS o acesso a variáveis não alinhadas gera exceções. Na arquitetura Intel isso não acontece. No máximo uma instrução que acesse uma variável não alinhada sofrerá a penalidade de ser executada em um ou mais ciclos adicionais, ficando um pouquinho mais lenta. Então, num MIPS, se criar uma estrutura assim:

struct mystruct __attribute__((packed))
{
  char c;
  int x;
};

Se você criar uma variável da estrutura mystruct e tentar acessar o membro x, pode tomar um General Protection Failure na cara, ou uma excessão de falha de alinhamento. Ora, o código deveria funcionar para qualquer plataforma que possua um compilador gcc!

É por esse motivo que você pode observar, em códigos sérios e multi-plataforma, o intenso uso de diretivas do pré-processador:

#ifdef WIN32
#include <opengl.h>
#endif
#ifdef __linux__
#include <GL/gl.h>
#endif
#ifdef MAC_OS
#include <agl.h>
#endif

Ou algo parecido! Não existe tal coisa como código universal! Se quer universalizar seu código, tem que fazer por si mesmo!

Assim, usar assembly num código em C garante apenas que, para certas plataformas, o código funcionará. Para outras, o código tem que ser diferente. Isso é verdade tanto para códigos em assembly in line quanto para o próprio código em C/C++. Lembre-se que, mesmo nas especificações ISO da linguagem, existem recursos dependentes de implementação (obtenha uma dessas especificações e leia!).

Então, mais uma vez, minha dica para você: Aprenda assembly! Não tenha medo de usá-lo, mas use-o com moderação e cuidado.

Anúncios

Um comentário sobre “Assembly faz bem pra você, mas com moderação…

  1. Artigo elogiável, mas o exemplo sobre o erro de interpretação merece um especial: o reconhecimento que o erro maior, senão o único, foi do professor.
    O grande mal de programadores é achar que o usuário tem que “saber de antemão” o conceito ou a metáfora a ser explicada.
    Vejo isto sempre nas tirinhas do “vidadeprogramador”… As situações reais deste tipo são inúmeras. Normalmente os programadores “ficam rindo” da “suposta estupidez” do usuário, mas não enxergam que o erro foi deles mesmos em não explicitar bem o ponto ou projetar uma interface pueril. É só ler a corriola nos comentários. Uma leva de ingênuos que, além de não saber nem escrever em português, ainda insiste em citar situações análogas, ainda mais esdrúxulas.
    Deve ser mais fácil rir da “estupidez” alheia ao invés de reconhecer que o ponto exposto não foi claro, ou gerou uma boa metáfora, caracterizando assim a incompetência do próprio programador, aliás dupla: uma por errar e outra por não reconhecer o erro.
    Sobre assembly: nunca vou me considerar suficientemente bom enquanto não dominar, assembly. Ou melhor, lógica e eletrônica digital.Tem uns vinte anos que estudo (e repito) o assunto. E ainda não entendi direito. Saber é uma coisa – fazer uns “programinhas”, rotinas, etc – dominar – aplicar o potencial para extrair “suco de bits” de um processador, mesmo que simples, é outra completamente diferente. Isto sim seria “amestrar” o assunto. Estou bem longe disto…

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s