MANUAL PRÁTICO DE PERFORMANCE By Jorge Luis Bachtold INTRODUÇÃO A melhoria de performance, tanto em PROGRESS quanto em qualquer outra linguagem envolve normalmente uma série de fatores. Não existe nenhum comando “mágico” que acrescentado a um programa vai torna-lo muito mais rápido. Na maioria das vezes, uma melhoria na forma de funcionamento do programa, tornandoo mais “inteligente” e menos repetitivo possibilita uma melhoria substancial de performance. Entretanto, mesmo os programas corretamente escritos podem se beneficiar de alguns recursos que o PROGRESS disponibiliza para torna-los mais ágeis. São esses recursos que serão analisados a seguir. VARIÁVEIS A menos que a aplicação não permita, utilize sempre variáveis NO-UNDO, pois elas não utilizam controle de transações, evitando escritas no arquivo LBI local, o que melhora a performance. Procure também evitar as variáveis LIKE, a menos que essas variáveis sejam utilizadas para exibição de informações na tela. Para variáveis internas, utilize a declaração AS, sem FORMAT nem LABEL. DEF VAR c-it-codigo AS CHAR NO-UNDO. Evite também as variáveis SHARED, a não ser que a aplicação exija. Se as chamadas a programas externos forem substituídas por PROCEDURES internas, a utilização de variáveis SHARED/NEW SHARED torna-se desnecessária. Essa técnica será mostrada com mais detalhes mais à frente. Quando forem utilizadas variáveis do tipo EXTENT, inicialize seu conteúdo com apenas um comando ASSIGN, ao invés de utilizar um loop, o que é muito mais lento. Certo: DEF VAR c-it-codigo AS CHAR extent 10 NO-UNDO. ASSIGN c-it-codigo = "A". Errado: DEF VAR c-it-codigo AS CHAR extent 10 NO-UNDO. DEF VAR i-cont AS integer NO-UNDO. DO i-cont = 1 TO 10: ASSIGN c-it-codigo[i-cont] = "A". END. ASSIGN Sempre utilize o comando ASSIGN para carregar valores para variáveis/campos. Procure agrupar o máximo de variáveis possíveis em um mesmo ASSIGN. Certo: ASSIGN c-it-codigo = "item-1" i-op-codigo = 10 c-cod-roteiro = "rot-1". Errado: c-it-codigo = "item-1". i-op-codigo = 10. c-cod-roteiro = "rot-1". BUFFER-COPY Este é um comando novo, introduzido na versão 8 do PROGRESS, que possibilita uma boa melhoria de performance, quando bem utilizado. Ele realiza a cópia de valores entre dois BUFFERS de registro, podendo trabalhar inclusive com TEMP-TABLES. Abaixo temos um exemplo típico da utilização do BUFFER-COPY: DEF TEMP-TABLE tt-item NO-UNDO LIKE item USE-INDEX codigo. FOR EACH item NO-LOCK: CREATE tt-item. BUFFER-COPY item TO tt-item. END. O exemplo acima copia todos os campos da tabela item para uma TEMP-TABLE. Além de executar mais rapidamente, a possibilidade de erros de digitação é muito menor. Basta lembrar que a tabela item possui mais de 200 campos. Um detalhe que vale a pena mencionar, é que os campos da TEMP-TABLE precisam estar definidos exatamente com o mesmo nome dos campos da tabela, pois o BUFFER-COPY não realiza substituição de campos por similaridade. Por exemplo, o campo NR-ORD-PROD e NR-ORD-PRODU vão surtir o mesmo efeito em um comando ASSIGN, mas irão falhar em um comando BUFFER-COPY. Este comando também permite a utilização das cláusulas USING e EXCEPT, permitindo informar quais os campos que deverão ser copiados. Para maiores detalhes, é conveniente consultar o help. FIELDS A cláusula FIELDS, também conhecida por FIELD-LIST, permite ao programador definir quais os campos de determinada tabela um comando de busca irá trazer através da rede para a máquina cliente. Este recurso foi introduzido pelo PROGRESS 8 para melhorar a performance em sistemas cliente/servidor. Através dele é possível reduzir bastante o tráfego de rede e consequentemente a velocidade dos programas. A cláusula FIELDS pode ser utilizada com os comandos FOR (FOR EACH, FOR FIRST, FOR LAST) e nas definições de QUERYS. FOR FIRST/LAST Os comandos FOR FIRST e FOR LAST podem substituir os comandos FIND FIRST e FIND LAST respectivamente. A grande vantagem do comando FOR é possibilitar a utilização da cláusula FIELDS, que permite definir quais os campos que serão enviados pela rede do servidor para o cliente. A utilização destes comandos deve ser feita com alguns cuidados: - - - - Caso a tabela a ser lida possua poucos campos e todos ou quase todos os campos serão utilizados após o comando FOR, não é vantajoso utiliza-lo; Os campos que não forem incluídos na cláusula FIELDS não estarão disponíveis para uso pelo programa, entretanto o PROGRESS NÃO emitirá mensagens de erro em tempo de compilação. Apenas serão emitidas mensagens de erro durante a execução do programa; A utilização do FOR com a cláusula FIELDS é vantajosa apenas em ambiente Cliente/Servidor, onde os registros transitam pela rede. Em ambientes HOST BASED (como o Magnus I), não apresentam nenhuma vantagem. Embora o comando permita, NUNCA deve ser utilizada a cláusula BY, pois nem o FOR FIRST e nem o FOR LAST realizam a ordenação antes de retornar o registro. A cláusula BY pode ser utilizada apenas com FOR EACH. Utilize FOR FIRST/FOR LAST apenas para leituras NO-LOCK. Para a leitura de registros que serão atualizados, é mais conveniente a utilização do FIND EXCLUSIVE-LOCK. A sintaxe mais comum para os comandos FOR FIRST/FOR LAST é a seguinte: FOR FIRST item FIELDS (it-codigo perm-saldo-neg pm-ja-calc) WHERE item.it-codigo = c-it-codigo NO-LOCK: END. FOR EACH O comando FOR EACH também permite a utilização da cláusula FIELDS, o que agiliza bastante a execução deste comando. O exemplo abaixo é cerca de 3 vezes mais rápido com a cláusula FIELDS: FOR EACH item FIELDS (it-codigo) NO-LOCK: END. Procure aninhar o máximo de leituras possíveis no mesmo comando. Certo: FOR EACH item FIELDS (it-codigo) NO-LOCK, FIRST estrutura FIELDS (it-codigo) WHERE estrutura.it-codigo = item.it-codigo NO-LOCK: DISP estrutura.it-codigo. END. Errado: FOR EACH item FIELDS (it-codigo) NO-LOCK: FIND estrutura WHERE estrutura.it-codigo = item.it-codigo NO-LOCK NO-ERROR. IF avail estrutura THEN DISP estrutura.it-codigo. END. Lembre-se que a cláusula FIELDS e a cláusula NO-LOCK devem ser informadas para cada componente do FOR EACH. Quando for realizar a atualização de registros, dentro de um FOR EACH, onde apenas alguns registros serão atualizados, procure realizar um FOR EACH com a cláusula NO-LOCK e um FIND EXCLUSIVE-LOCK apenas para os registros que serão atualizados. FOR EACH item FIELDS (it-codigo pm-ja-calc) NO-LOCK: IF item.pm-ja-calc THEN DO: FIND b-item WHERE ROWID (b-item) = ROWID (item) EXCLUSIVE-LOCK: ASSIGN b-item.log-1 = yes. END. DISP item.it-codigo. END. Lembre-se de utilizar um BUFFER do registro para realizar a atualização, senão o PROGRESS vai emitir uma mensagem de erro. FIND CURRENT O comando FIND CURRENT pode ser utilizado para realizar a atualização de um registro já tenha sido lido com um FOR FIRST. FOR FIRST item FIELDS (it-codigo) NO-LOCK: IF fn-altera-item (item.it-codigo) THEN DO: FIND current item EXCLUSIVE-LOCK. ASSIGN item.log-1 = yes. END. TEMP-TABLES As TEMP-TABLES, se utilizadas corretamente, possibilitam grande auxílio na melhora de performance dos programas em PROGRESS. Procure defini-las como NO-UNDO, a menos que seja realmente necessário manter o controle sobre as alterações realizadas dentro das transações. A utilização da cláusula NOUNDO, além de torna-la mais rápida, ocupa menos espaço em disco na estação do cliente. Procure ser o mais “mesquinho” possível na criação de índices, pois eles tornam a inclusão de registros mais lenta. Por outro lado, na grande maioria das vezes, é excelente definir um índice primário e único baseado nos FINDS que forem executados no programa. Não utilize os comandos FOR FIRST, FOR LAST e CAN-FIND para TEMP-TABLES, pois eles não irão proporcionar nenhum ganho de performance, afinal todas as informações da TEMP-TABLE estão armazenadas localmente e não irão transitar pela rede. Da mesma forma, não é necessário utilizar as cláusulas NO-LOCK, SHARE-LOCK ou EXCLUSIVE-LOCK, pois não existe lock de registros para TEMP-TABLES. O PROGRESS simplesmente vai ignorar essas cláusulas. Sempre que uma TEMP-TABLE for passada como parâmetro, esqueça seu ROWID, pois ele será alterado. Os FINDS com ROWID são os mais rápidos, mas se a TEMP-TABLE for passada como parâmetro será necessário utilizar o índice primário da TEMP-TABLE para posiciona-la. Procure evitar a criação de TEMP-TABLES LIKE, pois apesar de precisar de menos trabalho para defini-las, elas copiam todas as definições da tabela original, inclusive os índices. Apenas utilize TEMP-TABLES LIKE se realmente precisar de todos ou de quase todos os campos da tabela original. Se FOR realmente necessária a criação de uma TEMP-TABLE LIKE, utilize a cláusula USE-INDEX, o que fará com que apenas o índice informado seja trazido da tabela original para a TEMP-TABLE. DEF TEMP-TABLE tt-item NO-UNDO LIKE item USE-INDEX codigo. Procure evitar a utilização de TEMP-TABLES SHARED e NUNCA utilize TEMP-TABLES GLOBAL SHARED. USE-INDEX Procure não utilizar a cláusula USE-INDEX com FIND, FOR EACH e outros comandos de busca de registros, a menos que você realmente queira forçar a utilização de um índice. Em 99% dos casos, o PROGRESS irá selecionar o melhor índice para satisfazer a consulta com a melhor performance possível. Além disso, caso o índice seja modificado, seu programa poderá não mais estar aproveitando ao máximo o índice selecionado, se forem incluídos/modificados ou eliminados campos que faziam parte do índice originalmente. ROWID As buscas utilizando o ROWID são as mais eficientes. Sempre que possível, procure utilizar o ROWID da tabela para realizar uma busca. Se o programa permitir, armazene o ROWID utilizado em uma TEMP-TABLE para utilização posterior, caso os mesmos registros sejam acessados várias vezes durante a execução do mesmo. RUN O comando RUN permite a execução de PROCEDURES internas, programas externos e chamadas para DLL´s criadas através de outras linguagens. O objetivo deste tópico é analisar as chamadas para programas externos. Todo programa externo (.P ou .W) reside em disco, não sendo carregado para a memória no momento da execução do programa “pai”. Eles serão apenas trazidos para a memória quando da execução do comando RUN. Dependendo do tamanho do programa a ser chamado, de onde ele está localizado (em um servidor de arquivos da rede, por exemplo) e da velocidade do meio de armazenamento/transporte utilizado, esse tempo pode ser bastante longo. O PROGRESS dispõe de alguns parâmetros de inicialização da seção (por exemplo o –q) que diminuem o tempo necessário para a execução de uma chamada externa. Entretanto, mesmo com um ambiente otimizado, as chamadas externas devem ser evitadas ao máximo. Um dos mais promissores substitutos das chamadas para programas externos são as PROCEDURES internas (PI´s), que serão tratadas a seguir. Além disso, dependendo da situação, uma boa solução pode ser a utilização de INCLUDES ou a execução de uma chamada em modo persistente, que também serão tratadas a seguir. Como comparativo de performance, seguem abaixo dois exemplos de código. O primeiro utiliza uma chamada para programa externo e o outro utiliza uma PROCEDURE interna. Exemplo 01: /* A1.P */ DEF NEW SHARED TEMP-TABLE tt-item NO-UNDO LIKE item USE-INDEX codigo. FOR EACH item NO-LOCK: RUN B1.p (BUFFER item). END. /* B1.P */ DEF SHARED TEMP-TABLE tt-item NO-UNDO LIKE item USE-INDEX codigo. DEF param BUFFER b-item FOR item. CREATE tt-item. BUFFER-COPY b-item TO tt-item. Exemplo 02: DEF TEMP-TABLE tt-item NO-UNDO LIKE item USE-INDEX codigo. FOR EACH item NO-LOCK: RUN pi-grava (BUFFER item). END. PROCEDURE pi-grava. DEF param BUFFER b-item FOR item. CREATE tt-item. BUFFER-COPY b-item TO tt-item. end PROCEDURE O primeiro exemplo é executado em 29 segundos e o outro em 23 segundos. Vale lembrar que o sub-programa B1.P é bem pequeno. Em condições reais, quanto maior o subprograma, maior será a diferença de performance. PROCEDURES As PROCEDURES internas são uma das mais eficazes armas para melhoria de performance. Além de serem chamadas mais rapidamente, possuem uma série de outras vantagens: - - Podem receber um nome mais explicativo com relação à função que realizam. Por exemplo, o nome PI-CRIA-RESERVAS-ORDEM é bem mais esclarecedor do que CPP/CP0302b.p. Como elas estão no corpo do programa, todos os BUFFERS e variáveis que forem declaradas no programa principal poderão ser acessadas pela PI, não precisando receber parâmetros e nem variáveis SHARED. Permite a definição de variáveis e BUFFERS locais, o que as torna perfeitas para execuções recursivas. Apesar de estarem no corpo do programa, cada PROCEDURE é carregada em seu próprio segmento executável, o que permite que cada uma delas tenha até 64KBYtes de tamanho, como qualquer programa externo. Uma técnica que torna a programação com PROCEDURES extremamente fácil e estruturada, é utilizada em algumas API´s do módulo de Produção do EMS 2.0, como a API de criação de ordens de produção (CPAPI301) e de reporte repetitivo (CPAPI009). Essa técnica consiste em definir o código das PROCEDURES em INCLUDES e depois incorporar essas INCLUDES ao corpo principal do programa, o que acaba tornando-o bem pequeno e permite uma grande estruturação no código. INCLUDES As INCLUDES podem ser utilizadas para substituir as chamadas para programas externos e também para PROCEDURES internas, com algumas restrições. Como o código contido nas INCLUDES é incorporado ao programa, elas permitem a execução do programa em velocidade máxima, sem perdas de performance por causa de chamadas para programas externos ou PROCEDURES. Entretanto, elas aumentam o tamanho do programa, muitas vezes superando o limite de 64KBYtes por segmento executável. Uma regra básica para as INCLUDES é que devem existir poucas chamadas repetidas para elas dentro dos programas e que devem preferencialmente ser utilizadas em locais extremamente críticos em relação à performance. PROGRAMAS PERSISTENTES Uma boa idéia da PROGRESS. Um programa sendo executado de forma persistente nada mais é que um programa externo, rodado através do comando RUN e que retorna um HANDLE para o programa chamador, permanecendo ativo na memória. Através desse HANDLE é possível a execução de qualquer PROCEDURE interna do programa, como se elas fizessem parte do código original do programa pai. Um exemplo bastante conhecido de programa persistente é o UT-ACOMP.P, que é chamado de forma persistente e depois permite a execução de várias PROCEDURES como a PI-ACOMPANHAR e PI-INICIALIZAR. Infelizmente, o recurso de execução persistente não é muito utilizado pelo EMS. Através dele seria possível uma maior separação da interface das regras de negócio, disponibilizando PROCEDURES dentro das API´s para realização de validações, verificações de saldo e outras tarefas que são realizadas pelos programas com interface antes da efetiva chamada da API. Além disso, o PROGRESS permite a execução de programas persistentes em um servidor RPC, realizando AS chamadas das PROCEDURES diretamente ao servidor, de forma transparente para o programa, tornando a execução ainda mais rápida. A sintaxe mais comum para execuções persistentes é: RUN cpp/cpapi001.p PERSISTENT SET h-handle. CAN-FIND A função CAN-FIND retorna um valor lógico YES caso encontre o registro informado. O CAN-FIND deve sempre ser utilizado quando apenas é necessária a confirmação da existência ou não do registro, pois executa de forma mais rápida do que o FIND ou o FOR FIRST. Como a verificação da existência do registro é feita no servidor, não haverá nenhuma informação transitando pela rede a não ser um valor lógico YES ou NO, o que é bem mais rápido do que o envio de um ou mais campos da tabela como acontece com o FIND ou o FOR FIRST. SHARE-LOCK Evite utiliza-lo. Como o default do PROGRESS para os comandos FIND e FOR é SHARE-LOCK, deve-se sempre acrescentar AS cláusulas NO-LOCK ou EXCLUSIVE-LOCK quando for o caso. BUFFER Sempre que for necessária a utilização de um registro em uma PROCEDURE ou um sub-programa externo, utilize a passagem do BUFFER como parâmetro, em vez de passar o ROWID e realizar um FIND por esse ROWID. Certo: /* A1.P */ FOR FIRST item FIELDS (it-codigo) NO-LOCK: END. RUN B1.P (BUFFER item). /* B1.P */ DEF param BUFFER b-item FOR item. DISP b-item.it-codigo. Errado: /* A1.P */ FOR FIRST item FIELDS (it-codigo) NO-LOCK: END. RUN B1.P (INPUT ROWID (item)). /* B1.P */ DEF INPUT param rw-item AS ROWID NO-UNDO. FIND item WHERE ROWID (item) = rw-item NO-LOCK. DISP item.it-codigo. Deve-se lembrar que se um registro é lido com NO-LOCK, ele permanecerá com o mesmo estado também no programa B1.P. O mesmo vale para a cláusula FIELDS, que mantém o efeito no sub-programa. No exemplo acima, apenas o campo it-codigo está disponível tanto no A1.P quanto no B1.P. Uma restrição à utilização de PARAM BUFFER é que não é possível passar o BUFFER para programas executados via RPC. WORK-FILES Os WORK-FILES ou WORK-TABLES possuem algumas vantagens de performance em relação às TEMP-TABLES, entretanto possuem várias deficiências que restringem o seu uso a apenas algumas situações. A principal deficiência é não permitir a utilização de índices e nem a realização de FIND para registros específicos, permitindo apenas a movimentação seqüencial, com FIND NEXT/PREV/FIRST/LAST embora permita o posicionamento de forma aleatória utilizando-se o ROWID. Como vantagem, destaca-se a velocidade na criação de registros, um pouco superior à das TEMP-TABLES. Como os WORK-FILES são armazenados exclusivamente em memória, não é possível a utilização dos mesmos para armazenamento de uma grande quantidade de registros. Outra desvantagem é não poderem ser passados como parâmetros. A utilização mais óbvia para os WORK-FILES seria em uma aplicação que iria criar registros de forma seqüencial, realizando depois a leitura dos mesmos também de forma seqüencial, na mesma ordem. CASE Um comando extremamente interessante e também pouco utilizado. O CASE é mais rápido que uma série de comandos IF...THEN...ELSE aninhados e possibilita uma estruturação maior no programa. Certo: CASE n: WHEN 1 THEN RUN pi-proc-1. WHEN 2 THEN RUN pi-proc-2. OTHERWISE RUN pi-proc-3. END CASE. Errado: IF n = 1 THEN RUN pi-proc-1. ELSE IF n = 2 THEN RUN pi-proc-2 ELSE RUN pi-proc-3. IF...THEN...ELSE O comando IF, em algumas situações pode ser substituído pelo CASE. Em outras não. Uma forma de utilização que embora facilite bastante tem reflexos negativos na performance é a combinação dos comandos ASSIGN e IF (em determinadas situações). O exemplo abaixo ilustra bem isso: Certo: IF x = 1 THEN ASSIGN n = 0. Errado: ASSIGN n = IF x = 1 THEN 0 ELSE n. Além disso, deve-se evitar a utilização das constantes lógicas com o comando IF, conforme ilustrado abaixo: Certo: IF l-teste THEN RUN pi-teste. IF NOT l-teste THEN RUN pi-nao-teste. Errado: IF l-teste = yes THEN RUN pi-teste. IF l-teste = no THEN RUN pi-nao-teste. Obs.: Nos comandos de busca (FIND, FOR) não deve ser utilizado o operador NOT, sob pena do PROGRESS realizar uma busca em toda a tabela. INDEXED-REPOSITION Essa opção, existente nas QUERYS e SMART-QUERYS do PROGRESS, torna a navegação muito mais rápida. Ela não pode ser utilizada se houverem JOINS na QUERY. TRIGGERS Existem muitas tabelas importantes do EMS que possuem TRIGGERS de WRITE, CREATE e DELETE. Uma das mais conhecidas é a tabela item que possui uma TRIGGER de WRITE bastante complexa. Esses programas são executados toda vez que o evento correspondente é executado em uma tabela. Por exemplo, a TRIGGER de WRITE do item é executada toda vez que alguma informação é alterada nessa tabela. Em muitos casos isso reduz bastante a performance. Dependendo da aplicação, os campos a serem modificados não tem qualquer interferência na lógica da TRIGGER, entretanto ela é executada assim mesmo. Uma melhoria de performance considerável pode ser conseguida desabilitando-se as TRIGGERS das tabelas que estão sendo atualizadas. Uma forma de fazer isso pode ser vista abaixo: ON WRITE OF item OVERRIDE DO: END. Este código irá criar uma TRIGGER “vazia” que sobrepõe a execução da TRIGGER original, evitando que esta seja executada. Obs.: Tenha cuidado na hora de desabilitar as TRIGGERS. Verifique se realmente isso vai prover um ganho significativo de performance e se você não estará desativando regras de negócio importantes para o produto. ÍNDICES A correta utilização dos índices das tabelas é uma das formas mais eficientes de melhoria de performance. Existem várias regras para um bom aproveitamento dos índices. Vamos analisar todas elas: - Quando realizar a busca de um registro utilizando um índice composto de vários campos, sempre procure utilizar EQUALITY MATCHES, ou seja, condições com o sinal de igual (“=”), na mesma seqüência em que são definidos no índice. Ex.: Índice código, da tabela oper-ord, do banco MGIND: nr-ord-prod, it-codigo, cod-roteiro, op-codigo. Procure sempre utilizar o máximo possível de EQUALITY MATCHES, a partir do primeiro componente do índice: Adequado: FOR EACH oper-ord WHERE oper-ord.nr-ord-prod = i-nr-ord-prod NO-LOCK: Melhor: FOR EACH oper-ord WHERE oper-ord.nr-ord-prod = i-nr-ord-prod AND oper-ord.it-codigo = c-it-codigo NO-LOCK: Rápido: FOR EACH oper-ord WHERE oper-ord.nr-ord-prod = i-nr-ord-prod AND oper-ord.it-codigo = c-it-codigo AND oper-ord.cod-roteiro = c-cod-roteiro NO-LOCK: Muito Rápido: FOR EACH oper-ord WHERE oper-ord.nr-ord-prod AND oper-ord.it-codigo AND oper-ord.cod-roteiro AND oper-ord.op-codigo NO-LOCK: Evite: = = = = i-nr-ord-prod c-it-codigo c-cod-roteiro i-op-codigo FOR EACH oper-ord WHERE oper-ord.nr-ord-prod = i-nr-ord-prod AND oper-ord.cod-roteiro = c-cod-roteiro AND oper-ord.op-codigo = i-op-codigo NO-LOCK: No exemplo acima, o PROGRESS apenas utiliza o primeiro componente do índice, não podendo utilizar os demais por causa da quebra de seqüência nos campos do índice. Esse exemplo é especialmente lento, podendo ser inclusive mais lento do que o exemplo abaixo: FOR EACH oper-ord WHERE oper-ord.nr-ord-prod = i-nr-ord-prod NO-LOCK: IF oper-ord.cod-roteiro <> c-cod-roteiro or oper-ord.op-codigo <> i-op-codigo THEN NEXT. NUNCA realize uma busca sem utilizar pelo menos o primeiro campo de um índice qualquer da tabela, mesmo que os demais campos não correspondam aos campos do índice. No exemplo acima, nunca deveria ser realizado um FOR EACH na tabela oper-ord sem a utilização do campo nr-ord-prod, a não ser que exista outro índice que permita esse tipo de busca. - INEQUALITY MATCHES (“<>”) devem ser evitados ao máximo, sob pena de obrigarem a varredura seqüencial de toda a tabela. Se for realmente necessária a utilização do operador lógico <>, utilize-o em um campo que não corresponda ao primeiro componente do índice utilizado. No nosso exemplo da tabela oper-ord, ele poderia ser aplicado para qualquer campo menos o nr-ord-prod, sendo que quanto mais abaixo na estrutura do índice, melhor. Ou seja, a utilização do operador lógico <> no campo op-codigo impacta muito menos na performance do que a utilização no campo it-codigo. - RANGE MATCHES (“<”, “>”, “<=”, “>=”, “BEGINS”) seguem a mesma recomendação dada para o operador lógico <>. Deve-se evitar utiliza-los nos primeiros componentes do índice, e NUNCA aplica-los ao primeiro campo do índice, mesmo que para os demais campos sejam utilizados EQUALITY MATCHES. - EVITE a utilização da função MATCHES, que não faz aproveitamento dos índices - A ordem dos campos na cláusula WHERE não importa, desde que a seqüência de componentes do índice seja mantida. FOR EACH oper-ord WHERE oper-ord.nr-ord-prod = i-nr-ord-prod AND oper-ord.it-codigo = c-it-codigo NO-LOCK: Equivale a: FOR EACH oper-ord WHERE oper-ord.it-codigo = c-it-codico AND oper-ord.nr-ord-prod = i-nr-ord-prod NO-LOCK: - Se for necessário utilizar a cláusula BY em um FOR EACH, procure sempre informar um campo que seja o primeiro componente de um índice. - NUNCA utilize um IF...THEN...ELSE em uma cláusula WHERE, sob pena de obrigar o PROGRESS a realizar uma busca seqüencial na tabela. Utilize variáveis auxiliares para receber um valor conforme a condição e realize a busca com essas variáveis. - Procure evitar ao máximo a utilização do operador lógico OR em uma cláusula WHERE. Regras para seleção de índices: Se múltiplos índices não podem ser selecionados, o PROGRESS segue as seguintes regras para escolher um índice: 1) 2) 3) 4) 5) 6) 7) 8) 9) 10) Um índice não é utilizado (ou necessário) se for fornecido um ROWID. Se a opção USE-INDEX é especificada, o PROGRESS utiliza o índice informado. Se a opção CONTAINS é usada, ele seleciona o WORD-INDEX. Se um índice é único e todos os componentes desse índice são comparados utilizando-se EQUALITY MATCHES (“=”) e o outro índice é não-único, o PROGRESS escolhe o índice único. O índice com maior número de EQUALITY MATCHES (“=”). O índice com uma comparação BEGINS (porque BEGINS é considerada como 2 RANGE MATCHES) O índice com maior número de RANGE MATCHES O índice com o critério de ordenação (BY). Isso acontece apenas se a cláusula WHERE não prover informações suficientes para a seleção de outro índice. Pelo nome do índice, em ordem alfabética. O índice primário. DICAS DE PERFORMANCE - NUNCA utilize as INCLUDES de tradução (ut-liter, ut-table, ut-field) dentro de loops. Sempre que for necessária a utilização de literais dentro de um loop ou de um FOR EACH, utilize variáveis para conter as literais e realize o ASSIGN dentro do loop com essas variáveis. Certo: {utp/ut-liter.i Literal} ASSIGN c-liter = return-value. FOR EACH item NO-LOCK: CREATE tt-item. BUFFER-COPY item TO tt-item ASSIGN tt-item.msg = c-liter. END. Errado: FOR EACH item NO-LOCK: CREATE tt-item. {utp/ut-liter.i Literal} BUFFER-COPY item TO tt-item ASSIGN tt-item.msg = return-value. END. Isso degrada a performance porque dentro da INCLUDE ut-liter (e da ut-field e ut-table também), existe uma chamada para um programa externo, que como já vimos, acaba degradando a performance. - Procure tornar o programa mais “inteligente”. Por exemplo, em um programa que realiza várias atualizações repetidas na tabela saldo-estoq, é interessante armazenar o ROWID utilizado da primeira vez e depois realizar a busca do saldo utilizando-se desse ROWID. - Caso realmente não seja possível a melhoria da performance do programa para os níveis desejados pelo usuário, procure dar-lhe algum feed-back, ou seja, um retorno do que está acontecendo durante o processo. Essa dica não aumenta realmente a performance do programa, mas faz com que o tempo passe mais rápido para o usuário: é muito mais agradável para ele esperar dois minutos de processamento e acompanhar o que está sendo feito do que ficar dois “longos” minutos olhando para a ampulheta do windows. - Procure utilizar o máximo de condições possíveis no WHERE, mesmo que essas condições não façam parte do índice, desde que preferencialmente sejam “EQUALITY-MATCHES”, ou seja, utilizem o operador lógico “=”, evitando o uso do NEXT. Certo: FOR EACH ord-prod WHERE ord-prod.nr-ord-prod >= i-ord-ini AND ord-prod.nr-ord-prod <= i-ord-fim AND ord-prod.valorizada = no NO-LOCK: DISP ord-prod.nr-ord-prod. END. Errado: FOR EACH ord-prod WHERE ord-prod.nr-ord-prod >= i-ord-ini AND ord-prod.nr-ord-prod <= i-ord-fim NO-LOCK: IF ord-prod.valorizada THEN NEXT. DISP ord-prod.nr-ord-prod. END. - NUNCA utilize funções na cláusula WHERE nos campos da tabela sendo procurada. Utilize as funções sempre nos valores a serem localizados. Certo: FOR EACH movto-estoq WHERE movto-estoq.dt-trans = DATE (c-data) NO-LOCK: END. Errado: FOR EACH movto-estoq WHERE STRING (movto-estoq.dt-trans) = c-data NO-LOCK: END. O segundo FOR EACH será executado de forma muito mais demorada, pois o PROGRESS não pode saber o resultado da função sem antes testa-la com todos os registros. Ou seja, o PROGRESS irá realizar uma busca seqüencial em toda a tabela. - Não utilize RAW-TRANSFER para passagem de parâmetros. O comando RAW-TRANSFER é útil apenas quando for necessária a compactação de todo um registro de uma tabela ou TEMP-TABLE em um campo RAW. Em aplicações normais, deve-se utilizar a passagem de parâmetros e TEMP-TABLES tradicional. - Existem situações, com SMART-QUERYS muito complexas, em que os programas do EMS apresentam uma navegação extremamente lenta. Nesses casos, pode ser possível a “quebra” da QUERY original em duas ou mais QUERYS menores, com um link de RECORD entre elas. Esse tipo de solução não pode ser utilizada sempre, dependendo de cada caso, mas pode auxiliar bastante na performance, pois diminui o número de JOINS na QUERY e pode permitir a utilização do parâmetro INDEXED-REPOSITION. - Quando um BROWSE tiver uma QUERY com várias condições OR ou tiver condições que não utilizam índices, pode ser interessante transforma-lo em FREEFORM e utilizar um IF ou um CASE para determinar qual o OPEN QUERY que será utilizado. Esse código é colocado na TRIGGER de OPEN-QUERY IF l-nao-iniciadas AND l-liberadas AND l-alocadas AND l-separadas AND l-requisitadas AND l-iniciadas AND l-finalizadas AND l-terminadas THEN OPEN QUERY {&SELF-NAME} FOR EACH tt-niveis NO-LOCK, ~ EACH ord-manut WHERE ord-manut.cd-tag = tt-niveis.cd-tag-pai NO-LOCK BY ord-manut.dt-manut. ELSE OPEN QUERY {&SELF-NAME} FOR EACH tt-niveis NO-LOCK, ~ EACH ord-manut WHERE ord-manut.cd-tag = tt-niveis.cd-tag-pai AND (( ord-manut.estado = 1 AND l-nao-iniciadas ) or ( ord-manut.estado = 2 AND l-liberadas ) or ( ord-manut.estado = 3 AND l-alocadas ) or ( ord-manut.estado = 4 AND l-separadas ) or ( ord-manut.estado = 5 AND l-requisitadas ) or ( ord-manut.estado = 6 AND l-iniciadas ) or ( ord-manut.estado = 7 AND l-finalizadas ) or ( ord-manut.estado = 8 AND l-terminadas ) ) NO-LOCK BY ord-manut.dt-manut. - Lembre-se que um programa deve ser rápido também no cliente, e não apenas durante o desenvolvimento. Muitas vezes, a forma de utilização ou a carga de trabalho a que o programa é submetido no cliente influencia bastante na performance do mesmo. Um fator que costuma ter grande importância em relação à performance é o travamento (lock) de registros. Muitas vezes um programa é bem rápido quando executado em ambiente de desenvolvimento, mas torna-se bastante lento no cliente, por causa do travamento de registros, quando existem dois ou mais usuários operando o mesmo programa simultaneamente. Procure travar os registros para atualização durante o menor tempo possível. Procure também evitar que o programa pare devido a um registro travado, quando ele tem a possibilidade de processar outros registros. Deve-se entretanto, tomar alguns cuidados ao realizar a implementação acima, evitando a ocorrência de dead-locks. Um bom exemplo de código “inteligente”, que evita ao máximo o travamento de registros e a ocorrência de dead-locks é a API de geração de movimento de estoque CEP/CEAPI001.P, que incorpora, inclusive, o conceito de time-out para execução em RPC, evitando que um registro fique travado permanentemente.