• Nenhum resultado encontrado

YAP+ utiliza a compilação dinâmica [11] não apenas para poder usar informações sobre tipo dos argumentos de um predicado, mas também para determinar quais partes da apli- cação deverão ser otimizadas. A figura 5.2 mostra o processo de compilação dinâmica do sistema.

Figura 5.2: Processo de Compilação.

Quando um predicado é invocado pela primeira vez, ele é sempre interpretado. Se ele é executado freqüentemente, ele é compilado e conseqüentemente otimizado utilizando informações sobre tipos de dados. Ter um compilador rápido é essencial para reduzir as pausas de compilação em um sistema interativo que utiliza a compilação dinâmica.

O sistema deve descobrir oportunidades para a compilação sem a intervenção do pro- gramador. Em particular, ele deve decidir:

Quando compilar. Quanto tempo deve ser esperado para que as informações sobre o tipo dos argumentos estejam consistentes?

O que compilar. Quais predicados se beneficiarão das informações coletadas?Quais caminhos compilar. Quais os caminhos mais executados?

As subseções seguintes discutem cada uma destas questões.

5.2.1 Quando Compilar

O sistema de compilação utiliza contadores para detectar candidatos a serem compilados [106]. Cada cláusula interpretada possui seu próprio contador. Na entrada de seu código, o ambiente incrementa o contador e o compara com um determinado limite. Se o contador excedeu o limite, o sistema de compilação decide se a cláusula deveria ser compilada.

Contadores foram propostos apenas como um primeiro passo, na proposta original. Porém, o momento do acionamento do sistema de compilação (“quando”) é menos im- portante para um bom resultado de compilação do que o mecanismo de seleção (“o que”). Desde que a técnica baseada em contadores tem se mostrado uma boa opção [106], no de- senvolvimento de YAP+ não tem sido investigado novos mecanismos. Contudo, existem algumas questões relativas a utilização de contadores, que merecem ser observadas:

Idealmente, o sistema deveria compilar apenas aqueles predicados cujo custo de

otimização fosse menor do que o benefício obtido através das futuras invocações do predicado otimizado. Naturalmente, o sistema não conhece quão freqüente um

predicado será executado no futuro, mas em uma medida baseada em taxas também o passado é esquecido: um predicado que executa com menor freqüência que uma taxa de execução mínima nunca acionará o processo de compilação, ao menos se ele for executado muitas vezes.

O limite de invocação não deveria ser uma constante, pelo contrário, ele deveria

depender de um predicado em particular. O que os contadores estão realmente

tentando medir é quanto tempo de execução é gasto executando um código in- terpretado. Então, um predicado que se beneficiaria muito da otimização deveria incrementar mais rapidamente seu contador (ou ter um baixo limite) do que um

predicado que não se beneficiaria do processo de otimização. Na prática é difícil estimar o impacto de otimizar um predicado em particular.

Como o tempo de vida deveria ser adaptado quando a aplicação fosse executada

em uma máquina mais rápida (ou mais lenta)? Supondo que o limite do contador

original seja 1000 chamadas, mas que o sistema agora é executado em uma má- quina que é duas vezes mais rápida. Deveria este parâmetro ser alterado, e se sim, como? Pode-se ver a máquina mais rápida como um sistema onde o tempo real seja reduzido a metade, e reduzir o limite para 500 chamada. Mas, pode-se argumentar que a taxa limite de invocação é absoluta: se um predicado executa menos que N vezes por segundo, não tem de ser compilado.

Similarmente, deveria o limite de invocação do compilador ser tempo real, tempo

de CPU, ou alguma unidade específica de máquina? Intuitivamente, usar tempo

real parece um erro, dado que interferências de outras tarefas ou do usuário influen- ciariam no processo de compilação. Usar tempo de CPU também tem problemas: por exemplo, se a maior parte do tempo é gasta na coleta de lixo ou no processo de compilação, o tempo de vida é efetivamente diminuído desde que predicados interpretados gastam pouco tempo para executar e incrementam seus contadores de invocação. Por outro lado, este efeito pode ser desejável: se pouco tempo é gasto em código Prolog compilado, otimizar este código não acarretará em um aumento de desempenho significante.

5.2.2 O Que Compilar

Quando o contador atinge um determinado limite, o sistema de compilação é invocado para decidir se a cláusula deve ser compilada. Uma estratégia simples seria sempre com- pilar a cláusula cujo contador atingiu o determinado limite, pois é óbvio que ela foi invo- cada freqüentemente. Porém, está estratégia não é sempre a mais eficiente. Por exemplo, supondo que a cláusula que excedeu o limite seja formada apenas pelas instruções:

get_num 1, A1 proceed

otimizar esta cláusula não geraria o melhor ganho, a solução ideal é integrar a cláusula com a cláusula que a invocou. Em geral, para encontrar um boa candidata à compilação

Inspecionando a clásula que excedeu o limite, o sistema de compilação verifica se esta é uma boa candidada para a compilação. Caso verdadeiro, o compilador é invocado para otimizá-la. Após gerar código nativo para esta cláusula, o sistema de compilação instala no ambiente de execução esta nova versão. Se nenhuma candidata é encontrada, o interpretador continua a execução. O sistema seleciona a cláusula a ser compilada examinando diversas métricas. Para uma cláusulac, os seguintes valores são definidos:

c.contador: número de vezes que o predicado foi invocado.c.tamanho: número de instruções YAAM.

O compilador compila uma cláusula de cada vez, e seleciona a que satisfaz as se- guintes condições:

c.contador > MinInvocações. Esta condição assegura que o predicado foi executado vezes suficiente para considerar o tipo dos seus argumentos.

c.tamanho >= TamLimite. Esta última, elimina os predicados que não obteriam nenhum ganho de desempenho.

Estas regras assumem que as cláusulas executadas freqüentemente são boas candidatas à otimização. Além disto, estas regras fornecem um excelente ambiente para a detecção das regiões freqüentemente executadas do programa.

5.2.3 Quais Caminhos Compilar

Programas geralmente contêm caminhos raramente ou nunca executados. Compilá-los pode causar efeitos adversos tais como reduzir a eficiência do código gerado. Por exem- plo, um compilador otimizador pode gastar muito tempo aplicando agressivas otimizações em código raramente executado. O problema é que procedimentos fornecem uma maneira conveniente de particionar o processo de compilação, mesmo que não sejam necessaria- mente a melhor unidade para realizar otimizações. Uma abordagem diferente é tentar compilar apenas aquelas partes que são executadas freqüentemente.

Em um sistema de compilação dinâmica, esta estratégia é usual. Primeiro, compila- dores dinâmicos podem se utilizar da vantagem de obter em tempo de execução infor- mações estruturais e comportamentais da aplicação e usá-las no processo de seleção de regiões. Segundo, eles são sensíveis ao overhead de compilação, e esta técnica pode si- gnificantemente reduzir o tempo total de compilação e o tamanho do código. Terceiro,

eles podem evitar gerar código fora das regiões selecionadas até o código ser realmente executado.

O sistema de compilação do YAP+ utiliza a técnica de compilação baseada em re- giões. Procedimentos (ou melhor, cláusulas) não são tratados como a unidade de compi- lação. Ao invés disto, YAPc seleciona apenas aquelas porções que são identificadas como caminhos freqüentes. O termo região se refere a nova unidade de compilação, que resulta da exclusão de todas porções raramente ou nunca executadas.

O compilador otimizador seleciona da cláusula que está sendo compilado, os camin- hos freqüentes observando as informações sobre o tipo dos seus argumentos. Desta forma, o compilador reduz e otimiza o grafo de fluxo de controle da clásula que está sendo com- pilada.