Objetivo
Este documento tem como objetivo informar algumas dicas rápidas de performance que podem ser aplicadas no dia-a-dia. As regras são simples e seus efeitos, isoladamente, podem não apresentar grandes resultados, entretanto, seguindo a uma maior quantidade de regras, a melhoria de performance é considerável.
Muito dos problemas envolvem uso de comandos que causam impactos, enquanto outros problemas são causados por má utilização de comandos existentes, isto é, usar um comando que se substituído por outro melhora performance, podendo até diminuir consumo de memória.
Um fator importante a ressaltar, é que a performance não é resultado apenas de um comando mal aplicado, mas, também, por um consumo elevado de memória. Dependendo do tipo de programação, o arquivo compilado poderá crescer, aumentando então, o consumo de memória. A seguir, serão descritos as dicas para melhoria de performance:
- Utilização do comando IF repetidamente.
- Utilização de blocos para agrupar comandos.
- Utilização do comando IF para o uso do método LOAD-MOUSE-POINTER.
- Utilização do comando CASE.
- Testes de variáveis lógicas.
- Utilização do comando CAN-FIND.
- Redução de transações.
- Utilização de TEMP-TABLE do tipo global.
- Utilização do parâmetro NO-UNDO em definição de variáveis.
Utilização do comando IF repetidamente
O comando IF é um dos piores no ponto de vista de performance, entretanto, sua utilização é inevitável. Porém, é possível, em alguns pontos, fazer um melhor uso deste comando. Caso não seja possível substituir o comando IF por outro comando (CASE por exemplo), por motivos de lógica de programação, o uso adequado já auxilia na performance. A substituição dos comandos IF´S colocados um após o outro por uma encadeação do comando pode melhorar de maneira considerável a performance, isto é:
Trocar:
IF THEN DO:
comandos...
END.
IF THEN DO:
comandos...
END.
IF THEN DO:
comandos...
END.
IF THEN DO:
comandos...
END.
IF THEN DO:
comandos...
END.
Por:
IF THEN DO:
comandos...
END.
ELSE
IF THEN DO:
comandos...
END.
ELSE
IF THEN DO:
comandos...
END.
ELSE
IF THEN DO:
comandos...
END.
ELSE
IF THEN DO:
comandos...
END.
Desta maneira, as condições 2,3,4 e 5 só serão executadas caso necessário, enquanto que no primeiro caso, todas as condições são executadas.
É bom levar em consideração que este exemplo depende, logicamente, da necessidade do programa. Se for possível fazer a troca deste IF por um comando CASE, a performance pode ser melhorada.
Utilização de blocos para agrupar comandos
A utilização de blocos para agrupar comandos, pode aumentar desnecessariamente o arquivo compilado (RCODE), diminuindo assim a performance do produto.
Exemplos:
Trocar:
IF THEN DO:
ASSIGN VAR1 =
VAR2 =
VAR3 = .
END.
ELSE DO:
ASSIGN VAR1 =
VAR2 =
VAR3 = .
END.
Por:
IF THEN
ASSIGN
VAR1 =
VAR2 =
VAR3 = .
ELSE
ASSIGN
VAR1 =
VAR2 =
VAR3 = .
OBS: É importante ressaltar que neste caso, existe apenas um comando após o THEN e/ou ELSE. Entretanto, é necessário tomar cuidado, quando utilizar uma INCLUDE como código dentro de um possível bloco. Como não é possível saber o que há dentro da INCLUDE, por precaução, é aconselhável utilizar o bloco.
Exemplo:
IF THEN DO:
{INCLUDE}
END.
ELSE DO:
{INCLUDE}
END.
Agrupamento de comandos:
Trocar:
DO WITH FRAME {&FRAME-NAME}:
ASSIGN
VAR1 =
VAR2 =
VAR3 = .
END.
Por:
ASSIGN
VAR1 IN FRAME {&FRAME-NAME} =
VAR2 IN FRAME {&FRAME-NAME} =
VAR3 IN FRAME {&FRAME-NAME} = .
OBS: O motivo para esta troca é o mesmo do anterior, diminuição de arquivo compilado (RCODE), diminuindo então utilização de memória. Neste exemplo, é comum encontrar programas escritos desta maneira para “economizar” a digitação do parâmetro IN FRAME {&FRAME-NAME}.
Utilização do comando IF para o uso do método LOAD-MOUSE-POINTER
Após alguns testes, foi percebido que a linha de comando:
IF CAMPO:LOAD-MOUSE-POINTER("") IN FRAME {&FRAME-NAME} THEN.
Pode ser substituída por:
CAMPO:LOAD-MOUSE-POINTER("") IN FRAME {&FRAME-NAME}.
Aparentemente o ganho com esta troca pode parecer pequeno, mas levando em consideração que muitos programas utilizam várias vezes esta técnica, a somatória destes pequenos ganhos pode vir a ser considerável.
Utilização do comando CASE
Como já foi dito, a utilização do comando IF encadeado pode melhorar a performance em relação a utilização de comandos IF repetidos, mas, ainda podem causar um impacto na performance de um programa. Caso seja possível, é aconselhável utilizar o comando CASE no lugar dos comandos IF existentes. O problema do comando CASE é que a condição deve ser simples, isto é, normalmente é utilizado quando necessita de comparações de igualdade para uma condição.
Exemplos:
Trocar:
IF THEN DO:
comandos...
END.
ELSE
IF THEN DO:
comandos...
END.
ELSE
IF THEN DO:
comandos...
END.
Por:
CASE :
WHEN THEN DO:
Comandos...
END.
WHEN THEN DO:
comando...
END.
WHEN THEN DO:
comando...
END.
END CASE.
Teste de variáveis lógicas
As comparações realizadas com variáveis lógicas em comandos, como por exemplo, as condições em um comando IF, são mais rápidas se não utilizam o sinal de igualdade para expressar a condição desejada.
Exemplos:
Trocar:
IF = YES THEN DO:
Comandos ...
END.
Por:
IF THEN DO:
Comandos ...
END.
Utilização do comando CAN-FIND
A utilização do comando CAN-FIND retorna TRUE ou FALSE quando utilizado, isto é, existe apenas a verificação se o registro existe ou não. O registro pesquisado não fica habilitado para nenhuma operação, nem mesmo para leitura. Ao contrário do comando FIND que pode tornar o registro habilitado até mesmo para escrita.
O principal ganho para a melhoria da performance, é que o comando CAN-FIND retorna apenas um valor lógico, enquanto o comando FIND busca todo o registro, ou apenas os campos especificados para o CLIENT, isto é, aumenta o tráfego de rede fazendo I/O e, várias vezes, desnecessariamente.
A principal funcionalidade do comando CAN-FIND é a necessidade de saber se um registro existe ou não, como por exemplo, em um cadastro. Ao verificar se a chave informada já existe, não é necessário trazer todo o registro, somente a informação de sua existência.
Exemplo:
Trocar:
FIND WHERE . = no-lock.
IF NOT AVAILABLE THEN DO:
CREATE .
ASSIGN ..
END.
ASSIGN ..
Por:
IF NOT CAN-FIND( WHERE . = ) THEN
CREATE .
ASSIGN ..
END.
ASSIGN ..
Redução de transações
A redução de transações significa mais memória livre, pois a quantidade de registros bloqueados é menor. Entretanto, para diminuir as transações, existe um aumento na complexidade da codificação dos programas.
Será descrito a seguir algumas dicas para diminuir transações:
- Criar sub-transações:
Para cada agrupamento de comandos que façam alteração de informações em um banco de dados, é criado uma transação no escopo do bloco atual que pode ser o programa como um todo ou um laço(DO TRANSACTION: … END.).
Exemplos:
Uma grande transação:
... /* Início do programa – Não acessa o banco de dados */
DO: = 1 TO 1000:
CREATE .
ASSIGN . = .
END.
... /* Finalização do programa – Não acessa o banco de dados */
OBS: A transação deste programa fica no escopo da programa (DO: … END.) e bloqueia 1000 registros. Para isto é consumida uma quantia de memória. É importante ressaltar que um simples comando DO não é suficiente para gerar uma transação. Caso só exista ele, como é o caso, o escopo da transação é o programa como um todo.
Uma pequena transação:
... /* Início do programa – Não acessa o banco de dados */
DO = 1 TO 1000:
DO TRANSACTION:
CREATE .
ASSIGN . = .
END.
END.
... /* Finalização do programa – Não acessa o banco de dados */
OBS: A transação deste programa é menor, pois fica no escopo da sub-transação (DO TRANSACTION: … END.) e bloqueia apenas 1 registro. Para isto é consumido menos memória do que no exemplo anterior.
- Utilizar o comando DO para laços (LOOP) simples ao invés do comando REPEAT:
É considerado um laço simples, aquele que não tiver envolvimento com transações. O comando REPEAT cria por default uma transação:
Transação desnecessária:
... /* Início do programa */
REPEAT = 1 TO 100:
ASSIGN = .
DISPLAY ...
END.
... /* Finalização do programa */
OBS: Foi criado desnecessariamente transações, ocupando então memória.
Correção:
... /* Início do programa */
DO = 1 TO 100:
ASSIGN = .
DISPLAY ...
END.
... /* Finalização do programa */
OBS: Não foi criado nenhuma transação, consumindo menos memória.
Utilização de TEMP-TABLE do tipo global
Não é aconselhável a utilização de TEMP-TABLE definida como uma variável global. O consumo de memória causado por uma TEMP-TABLE pode ser muito grande, dependendo ainda, da quantidade de campos e registros. Esta variável será eliminada da memória somente ao fechar a sessão PROGRESS.
Neste caso, a performance do produto é afetada como um todo e não só de um programa apenas.
É possível, em muitos casos, substituir uma TEMP-TABLE global por uma TEMP-TABLE compartilhada (NEW SHARED e SHARED) ou por passagem de parâmetros.
Utilização do parâmetro NO-UNDO em definição de variáveis
Variáveis declaradas como NO-UNDO, não necessitam ser controladas pelo PROGRESS, isto é, quando não é utilizado o parâmetro NO-UNDO, o arquivo *.LBI (local before image – este arquivo está no Client) necessita controlar os valores armazenados pela variável. Isto aumenta I/O diminuindo a performance.
Normalmente, as variáveis não precisam ser controladas caso haja problemas com o sistema, sendo assim, não é necessário ficar armazenando seus valores.