Acelerando o AdvPL – Parte 03

Introdução

Continuando os tópicos de performance e escalabilidade direcionados ao AdvPL, vamos hoje unir o útil ao agradável: Vamos abordar detalhes algumas boas práticas, inclusive algumas já mencionadas na documentação da TDN. Inclusive, enquanto este artigo era redigido, encontrei uma documentação muito interessante no link http://tdn.totvs.com/pages/viewpage.action?pageId=22480352, que abrange assuntos como as convenções da linguagem, padrões de design, práticas e técnicas, e inclusive desempenho.

Macroexecução em AdvPL

Como o foco deste tópico é justamente acelerar o AdvPL, vamos abordar um dos tópicos de desempenho (http://tdn.totvs.com/display/framework/Desempenho). Um dos testes realizados e publicados nesta seção da TDN foi uma comparação entre duas abordagens de exemplo de chamada de função, uma usando a macroexecução e outra usando a função Eval(), onde o teste realizado mostrou um melhor desempenho na macroexecução.

Fonte de testes

Baseado no fonte de testes da TDN, eu criei um fonte que simula quatro formas diferentes da chamada de uma função de processamento de exemplo, onde existe a passagem de dois parâmetros. Vamos ao código:

#INCLUDE "PROTHEUS.CH"
#DEFINE ITERATION_REPEAT 800000 // Repetições de testes
User Function EvalTest()
TesEcom1() // Teste 01 com "&" 
TesEcom2() // Teste 02 com "&"
TesEvalC() // Teste com Eval()
TesDiret() // Teste com chamada direta
Return
/* ---------------------------------------------------------------
Teste TesEcom1()
Usando macro-substituição, passando os parâmetros por fora da macro.
--------------------------------------------------------------- */
Static Function TesEcom1()
Local nX, nSeconds, nTimer
Local cRet := ""
Local cBloco := "FunTesExec"
nSeconds := Seconds()
For nX := 1 To ITERATION_REPEAT 
 cRet := &cBloco.(nX,10)
Next nX
nTimer := Seconds() - nSeconds
ConOut("("+procname(0)+") Tempo de execucao ....: " +str(nTimer,6,2)+' s.' )
Conout("("+procname(0)+") Operacoes por segundo.: "+str(ITERATION_REPEAT/nTimer,10))
Return
/* ---------------------------------------------------------------
Teste TesEcom2()
Usando macro-substituição, passando os parametros DENTRO da macro.
--------------------------------------------------------------- */
Static Function TesEcom2()
Local nX, nSeconds, nTimer
Local cRet := ""
Local cBloco
nSeconds := Seconds()
For nX := 1 To ITERATION_REPEAT 
 cBloco := "FunTesExec("+cValToChar(nX)+",10)"
 cRet := &(cBloco)
Next nX
nTimer := Seconds() - nSeconds
ConOut("("+procname(0)+") Tempo de execucao ....: " +str(nTimer,6,2)+' s.' )
Conout("("+procname(0)+") Operacoes por segundo.: "+str(ITERATION_REPEAT/nTimer,10))
Return
/* ---------------------------------------------------------------
Teste TesEvalC()
Usando Code-Block 
--------------------------------------------------------------- */
Static Function TesEvalC()
Local nX, nSeconds, nTimer
Local cRet := "" // Retorno do Bloco de Codigo
Local bBloco := {|p1,p2| FunTesExec(p1,p2)}
nSeconds := Seconds()
For nX := 1 To ITERATION_REPEAT
 cRet := Eval(bBloco,nx,10)
Next nX
nTimer := Seconds() - nSeconds
ConOut("("+procname(0)+") Tempo de execucao ....: " +str(nTimer,6,2)+' s.' )
Conout("("+procname(0)+") Operacoes por segundo.: "+str(ITERATION_REPEAT/nTimer,10))
Return
/* ---------------------------------------------------------------
Teste TesDiret()
Usando Chamada Direta
--------------------------------------------------------------- */
Static Function TesDiret()
Local nX := 0
Local nSeconds := 0
Local cRet := ""
nSeconds := Seconds()
For nX := 1 To ITERATION_REPEAT
 cRet := FunTesExec(nX,10)
Next nX
nTimer := Seconds() - nSeconds
ConOut("("+procname(0)+") Tempo de execucao ....: " +str(nTimer,6,2)+' s.' )
Conout("("+procname(0)+") Operacoes por segundo.: "+str(ITERATION_REPEAT/nTimer,10))
Return
/* ---------------------------------------------------------------
Funcao de Teste FunTesExec()
Apenas encapsula a função StrZero
--------------------------------------------------------------- */
STATIC Function FunTesExec(nExpr, nTam)
Local cNum
DEFAULT nExpr := 1
DEFAULT nTam := 1
cNum := StrZero(nExpr,nTam)
Return cNum

Resultados do teste

No meu notebook, os resultados obtidos no console do TOTVS AppServer foram estes :

(TESECOM1) Tempo de execucao ....: 3.16 s.
(TESECOM1) Operacoes por segundo.: 253325
(TESECOM2) Tempo de execucao ....: 21.12 s.
(TESECOM2) Operacoes por segundo.: 37879
(TESEVALC) Tempo de execucao ....: 3.16 s.
(TESEVALC) Operacoes por segundo.: 253084
(TESDIRET) Tempo de execucao ....: 2.34 s.
(TESDIRET) Operacoes por segundo.: 341297

Refletindo sobre estes tempos

Inicialmente, vamos ver o tempo mais rápido. Naturalmente, é uma chamada direta da função de processamento. Demorou 2,34 segundos para fazer 800 mil iterações. São aproximadamente 340 mil execuções por segundo. Depois vem os tempos de execução via macro (01) e Eval(), 3,16 segundos, aproximadamente 253 mil execuções por segundo. O tempo ficou 35 % maior para ambos os casos. Agora, o tempo mais lento, do segundo teste de macro-execução, demorou 21 segundos, apresentando um desempenho de 37 mil execuções por segundo, que ficou 9 vezes mais lento que o melhor resultado.

Agora, vamos olhar com uma lupa o “miolo” dos dois casos de teste com macro-execução, que é o foco principal deste post:

No primeiro teste, a chamada da função é realizada com uma forma de macro-substituição, onde apenas o nome da função a ser chamada está dentro da variável cBloco, e os parâmetros são passados explicitamente em cada chamada por fora da Macro.

cBloco := "FunTesExec"
For nX := 1 To ITERATION_REPEAT 
 cRet := &cBloco.(nX,10)
Next nX

No segundo teste, a macro é montada dentro de uma string, onde os parâmetros da função são montados dentro da string. Como a variável nX é local, ela não pode ser especificada diretamente, pois a visibilidade de resolução da macro não consegue pegar as variáveis locais.

For nX := 1 To ITERATION_REPEAT 
 cBloco := "FunTesExec("+cValToChar(nX)+",10)"
 cRet := &(cBloco)
Next nX

No primeiro teste, o texto da macro ( FunTesExec ) não muda durante as interações, o que permite ao AdvPL fazer um cache da resolução da macro, e acelerar o processo. Já no segundo teste, a macro é alterada com novos parâmetros a cada nova execução, o que exige que a macro seja recompilada na memória a cada iteração. Isto dá uma grande diferença de desempenho entre os dois ciclos de iterações.

Conclusão

Existem casos especiais onde a cópia dos parâmetros para uma macro precisam realmente ser feitas na chamada, mas agora não lembro de nenhum exemplo assim, “de cabeça”. Mas, para a maioria dos outros casos, onde você pode precisa fazer dinamicamente apenas a chamada de uma função dinâmica, cujo nome está em uma variável caractere, é mais rápido passar os parâmetros por fora.

Desejo a todos excelentes otimizações, um desempenho fantástico, e um ótimo final de semana 😀

Abraços, e até o próximo post 😉

Anúncios

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s