• Nenhum resultado encontrado

Implementações de Prolog tiveram um grande progresso no seu desempenho com o de- senvolvimento da WAM. Contudo, a velocidade de execução pode ser melhorada ainda mais. Diversas técnicas foram propostas:

1. reduzir a granularidade das instruções; 2. explorar determinismo;

3. especializar a unificação; e

4. utilizar a análise de fluxo de dados.

2.3.1 Reduzir a Granularidade das Instruções

A WAM mapeia de forma elegante a linguagem Prolog para uma máquina seqüen- cial. Algumas instruções da WAM podem ser bastante complexas, como por exemplo

get_structure e unify_value. Devido a isto muitas otimizações não são possíveis. Um

x(1).

Este predicado é compilado para: get_constant 1, A1 proceed

A instrução get_constant realiza duas operações. Primeiro, o argumento A1 é desre- ferenciado, segundo A1 é unificado com o valor 1. Algumas operações podem ser desne- cessárias. Por exemplo, se o predicado x(K) é chamado com um átomo desreferenciado, então a unificação deveria ser reduzida para um simples teste que verifica se o valor é o correto.

Granularidade grossa faz sentido num emulador, contudo para fins de otimização é importante reduzir a granularidade de cada instrução WAM.

O compilador otimizador desenvolvido por Tamura [68, 113] reduz a granularidade das instruções através da representação de cada instrução WAM como um subgrafo contendo simples operações como instruções de seleção sobre tags de tipos, saltos, atri- buições e desreferenciação. Em seguida, este grafo pode ser otimizado através de regras de reescrita.

2.3.2 Explorar o Determinismo

A maioria dos predicados escritos por programadores são intencionalmente escritos a fim de fornecerem apenas uma solução, isto é, eles são determinísticos. Infelizmente, usar índices para escolher a correta cláusula é ineficiente. Pois, para possíveis retrocessos existe a necessidade de salvar e restaurar o estado da máquina.

Significantes melhorias sobre a WAM são obtidas evitando retrocesso sobre predica- dos determinísticos. Turk [115] foi o primeiro a descrever otimizações que reduzem o tempo necessário para restaurar o estado da máquina durante um retrocesso.

SICStus Prolog [104, 86] reduz o overhead de um retrocesso criando pontos de es- colha em duas fases: primeiro, ele salva apenas uma pequena parte do estado da máquina, adiando salvar o restante do estado até o ponto na cláusula onde ela pode ser determinada através da unificação da cabeça e um simples teste.

[76] podia indexar qualquer argumento. SEPIA [79] incorporava heurísticas para decidir quais argumentos são importantes para uma seleção determinística. Este usava o primeiro argumento indexável de um predicado. Se existem várias possibilidades, o sistema utiliza o argumento que selecionará a menor quantidade de cláusulas.

Como será visto no capítulo 5, o sistema proposto utiliza type-feedback profiling [59]. A principal idéia deste tipo de profiling é extrair em tempo de execução o tipo mais comum da informação, e fornecé-la como parâmetro ao compilador. Desta maneira o compilador do sistema gera código nativo especializado para as cláusulas executadas.

2.3.3 Especializar Unificação

Significantes melhorias sobre a WAM são possíveis para a unificação. Turk [115] des- creve otimizações para compilar unificação, reduzir o overhead de explicitamente manter um bit de modo e remover alguns desreferenciamentos supérfluos e checagem de tag. Marien [75] descreve um método de compilar unificação no modo de escrita que usa um número mínimo de operações e evita instruções supérfluas de desreferenciação e checa- gem de tag. Van Roy [116] introduz uma simples notação e a estende para uma unificação do modo de leitura, mas este esquema gera código excessivo. O sistema Aquarius [117] modifica esta técnica para limitar a expansão do código. Meier [78] desenvolveu uma técnica que generaliza a idéia de Marien para os modos de escrita e leitura e consegue tamanho de código linear.

Beer [14] sugere o uso de uma representação simplificada de variáveis onde ligações são muito rápidas. Este trabalho introduz alguns novos tags para esta representação, eles são chamados “variáveis não inicializadas”, e mantém referências delas em tempo de execução. Ele mostra que o tempo de desreferenciação é reduzido significantemente.

Para reduzir o overhead da unificação é proposto utilizar duas versões para unificação, que poderá ser ainda otimizada pelo compilador otimizador desenvolvido.

2.3.4 Análise de Fluxo de Dados

Tradicionalmente, análise global de programas lógicos é usada para derivar informa- ções que serão utilizadas para melhorar a execução do programa. Informações de tipo e controle podem ser derivadas e usadas para aumentar a velocidade de execução e re- duzir o tamanho do código. Os algoritmos utilizados por Prolog são instâncias de um método geral chamado de interpretação abstrata [35]. A idéia é executar o programa sobre um domínio simples. Se um pequeno conjunto de condições for satisfeito, esta exe-

cução termina e seus resultados provêem uma correta aproximação de informações sobre o programa original.

Warren [126] foi o primeiro a estudar a praticidade da análise global em programação lógica. Ele descreve dois analisadores de fluxo de dados:

1. MA3, um analisador e-paralelo, e 2. Ms, um analisador experimental.

MA3 deriva tipos instanciados e mantém referências das estruturas e termos com- postos, enquanto Ms deriva tipos instanciados que não são variáveis. Seu trabalho de- mostra que ambos analisadores são efetivos em derivar tipos e não aumentam o tempo de compilação de forma significativa. Marien [77], Van Roy [117], e mais recentemente Morales [82] também obtiveram resultados similares.

Type-feedback profiling é bastante usual neste contexto para guiar na seleção dos ca-

minhos executáveis a partir de uma execução dinâmica. O compilador otimizador desen- volvido nesta tese compila apenas caminhos relevantes, e interpreta o restante do código.