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.
Download

Progress 4GL - 0785 - Manual Prático de Performance