Publicado por: hugolt | fevereiro 10, 2008

O primeiro artigo a gente nunca esquece…

Compiladores e Interpretadores

O que é um compilador?

Um compilador é um programa ou um conjunto de programas que “traduz” o código escrito em certa linguagem (código fonte) para outra determinada linguagem, não necessariamente linguagem de máquina. O arquivo gerado é chamado de código de saída.
O código fonte pode estar em um ou mais arquivos de texto e “guarda” o programa propriamente dito; o código de saída pode estar em um arquivo de texto ou em um arquivo binário com instruções. Ele é a junção, o resultado, da tradução do código fonte para uma linguagem alvo, geralmente a linguagem de máquina.
Generalizando bem, o compilador realiza dois processos sempre, o de assembling e linkage.
Vamos pegar como exemplo o famoso GCC e traduzir um código C para linguagem nativa da máquina.

#include <stdio.h>
int main(void)
{
printf("Hello World\n");
return 0;
}

chamando o gcc para gerar direto o executável, sem pular o passo de linkage:
% gcc -o hello hello.c
esse código gera a saída de hello.c direto para o executável chamado ‘hello’, escondendo de nós o processo do linker.
Mas se quisermos gerar simplesmente saída sem ser executável, um código objeto esperando pra ser ligado a outros (como bibliotecas/APIs), pode-se fazer com o gcc também, basta pular o processo de linkage usando a opção -c.
% gcc -c hello.c
Nesse caso temos agora um único arquivo gerado, o hello.o, que é o nosso código objeto. Como não temos mais arquivos no nosso simples programas, podemos gerar a saída logo, usando a opção -o.
% gcc -o hello.out hello.o
Agora temos o nosso `hello.out’ que é idêntico ao `hello’ gerado pelo comando acima e podemos executá-lo de forma análoga.

Uma das tarefas do compilador é analisar de todas as formas o programa antes de gerar a saída, analisando os pré-processamentos, procurando por erros léxicos, sintáticos, semânticos; pode também otimizar o código.

O que é um interpretador?

Um interpretador é um programa ou um conjunto de programas, que, como o próprio nome diz, “interpreta” o código fonte.
(Vago, não?)
Essa interpretação é feita de forma a pegar o código fonte e o executar em uma linguagem intermediária assim que acabar de analisar os comandos, atribuições e o que quer que esteja escrito no fonte. Diferente de um compilador o interpretador não gera nenhum outro arquivo, ele procura por erros (de forma parecida do compilador) sintáticos, semânticos, léxicos e caso não ache nada tenta executar as instruções em nesta linguagem intermediária. O CPU (Central Processing Unit) é um interpretador e apenas executa imediatamente as instruções que são passadas a ele.
Um exemplo de interpretador é o famosíssimo BASIC que foi tão usado décadas atrás.(Ressalto que Bill Gates ficou famoso por ter “criado” uma linguagem chamada BASIC pro Altair e na verdade a linguagem BASIC já existia, desde dos anos 60. O que ele fez foi criar um interpretador BASIC; coisa que não existia pro Altair até então).
Mas também existem linguagens interpretadas, tais como Python (pode ser compilada também), que estão se tornando cada vez mais usadas.

Veja que para executar um código python não precisamos compilar nada, apenas o interpretador é necessário. O código em python pode ser armazenado em um arquivo de texto, ou ser diretamente escrito e executado no interpretador, da mesma maneira que shell script.

hello.py:

+-----------------
#!/bin/env python
print 'Hello World'

-----------------+
% chmod a+x hello.py
% ./hello.py
Hello World

ou direto no interpretador:
% python
>>> print 'Hello World'
Hello World
>>>

Algumas vantagens e desvantagens

Como já deve ter percebido, a maior vantagem em compilação sobre interpretação é o tempo de execução do programa.
Peguemos o código C e o código Python que foram citados acima para comparar seus tempos de execução.

% time ./hello
Hello World


real    0m0.006s
user    0m0.000s
sys     0m0.010s

% time ./hello.py
Hello World
real    0m0.050s
user    0m0.040s
sys     0m0.010s

Fica bem visível a diferença entre os tempos, mesmo o código em python possuindo apenas uma única linha.
Agora vamos ousar um pouco e fazer um programa que mostre-nos números primos de 1 a 100 (incluindo) usando o mesmo algoritmo:
Programa em C
Programa em Python
Resultado de dois comandos “time”
Se diferença de tempo é notável em programas simples, imagine em um programa complexo.

A interpretação tem grande vantagem sobre compilação quando você está fazendo testes, aprendendo certa linguagem ou mesmo quando quer algum programa que rode sem alterar nenhuma linha sequer em qualquer máquina (o único requisito é o interpretador instalado).
Um código escrito em Python ou em Java roda em qualquer arquitetura de processador e sistema operacional (que possua uma versão do interpretador disponível) sem qualquer alteração. Porém esses mesmos códigos pecam no uso de memória. Em Python, por exemplo, não há declaração explícita de variáveis, assim tendo que haver uma porção de memória reservada para variáveis. Num código em C, toda e qualquer variável a ser utilizada tem que ser devidamente declarada, tirando vantagem sobre um código interpretado.
Uma outra desvantagem em compilação é o ciclo de debugging. Tratando-se de um código compilado, toda vez que for necessário executar o programa com variações de código precisa-se compilar tudo, formando assim um ciclo: editar-compilar-rodar-reparar erros (edit-compile-run-debug), e o tempo de compilação de um programa varia, podendo levar mais de minutos dependendo da complexidade do programa.
O código interpretado por sua vez tem certa vantagem sobre isso, pois não há compilação, então o ciclo fica: editar-interpretar-reparar erros (edit-run-debug), o que pode ser mais rápido que o ciclo de debugging em compilação, pois como não há compilação, o tempo do ciclo diminui.


Um pouco sobre JIT em Java

Não sou nenhum programador Java e nem sei tanto sobre a linguagem, mas posso falar um pouco. Java é uma linguagem orientada a objetos que tem ganho muito espaço, tanto é que até em celulares vemos Java. Porém, difere um pouco da interpretação citada acima.
Um compilador Java gera um tipo de código objeto conhecido por bytecode que é interpretado por uma máquina virtual (Java Virtual Machine, ou simplesmente JVM). Depois de gerado o bytecode a JVM pode fazer a interpretação dele da forma citada primeiramente, transformando para uma linguagem intermediária e depois executando o código.

Existe uma técnica conhecida como compilador JIT, Just In Time compiler, que tem sido muito utilizado em conjunto com a JVM. O compilador JIT é uma mescla do uso da velocidade de um programa compilado com a portabilidade de um programa interpretado. A cada vez que o código é executado numa JVM que utiliza JIT a porção inicial de código é traduzida para código de máquina, tendo um custo maior de memória e processamento no início da execução do programa e assim que for sendo requisitado algum trecho de código é compilado ao invés de ser interpretado. Nada é compilado mais de uma vez, pois tudo que foi compilado fica armazenado na memória esperando o uso, sem necessidade de compilar tudo outra vez. É uma forma de ganhar velocidade.
(Já é possível entender a razão de um programa Java ser lento comparado a um programa devidamente compilado).


Self-Hosting Compiler

Self-hosting é a técnica de produzir um programa a partir de uma versão diferente deste mesmo programa. Quando se fala em self-hosting compiler estamos falando de criar um compilador a partir do mesmo compilador. Vamos usar como exemplo o primeiro uso da técnica (exceto o uso com assemblers).
A linguagem LISP existia na década de 60, porém era interpretada. Com o interpretador foi criado um compilador, interpretado, claro. Porém, esse compilador podia compilar ele mesmo, e gerar então um compilador que não era interpretado.
O compilador rodava num interpretador LISP comum, ele compilaria o que fosse passado a ele da forma supracitada, e então foi passado o seu próprio código fonte e foi gerado um executável, dependente apenas da máquina. Porém com esse compilador foi possível abrir vários caminhos para portar para outras máquinas e essa técnica é usada em aulas de cursos na área de computação.
Atualmente pode-se juntar código interpretado com código compilado em LISP, parece ser uma idéia muito boa e um bom paradigma de programação pra se aprender. Você escreve o código, testa, se estiver tudo okay você compila e usa essa parte compilada em conjunto com as próximas interpretações do mesmo código ao invés de interpretar tudo, ganha-se tempo.


Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

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

Categorias

%d blogueiros gostam disto: