Short Circuit: Uma boa prática
Em diversas linguagens de programação – para as boas práticas em condicionantes, estrutura de repetições ou em outras validações – recomenda-se testar primeiro as opções que possuem mais chance de ocorrerem e que são menos custosas em termo de processamento. Isto evita que outras validações sejam efetuadas sem necessidade, fornecendo uma melhor performance para o código. Em PL/SQL não é diferente e para o processamento de uma grande massa de dados a adoção desta prática promove um ganho bastante significativo.
Agora devem está imaginando: “Isto é óbvio”. De fato. Contudo, nota-se que em grande parte das vezes esta boa prática não é implementada. Eu mesmo não me preocupo com isto em uma primeira implementação. Recentemente, uma importação de uma grande quantidade de dados diários estava fora do tempo que consideravam aceitável. Ao analisar o código foi percebido que existiam diversos IF’s e ELSE’s alinhados. A primeira ação foi verificar o comportamento dos inserts e updates. Quais ocorriam mais? Foi constatado que uma condição acontecia 76% das vezes e esta era testada apenas depois de 7 outras serem avaliadas. A ação foi reclassificar o alinhamento dos IFs e ELSEs em ordem de maior ocorrência, o que já promoveu um ganho no tempo de resposta. Esta boa prática, foi somado a utilização de Forall, Bulk Collect e Insert First o que tornou a execução ainda mais performática.
Agora vamos aos exemplos com intuito de comparar os tempos de execução. Para a estrutura abaixo foi feito um loop para ocorrer 5 milhões de vezes e alinhado vários IF e Elses. A busca que desejamos é uma string com conteúdo TESTE 1. Para o bloco com boas práticas a busca será satisfeita já no primeiro IF, logo não será necessário efetuar os demais testes poupando processamento. Já no segundo bloco, agora sem a boa prática, a busca será satisfeita apenas no último IF, obrigando a passagem por todas as outras condições.
SET serveroutput ON
DECLARE
v_time_i_one NUMBER;
v_time_i_two NUMBER;
v_time_f_one NUMBER;
v_time_f_two NUMBER;
v_qtd_loop NUMBER := 5000000;
v_my_value VARCHAR2(10) := 'TESTE 1';
BEGIN
---- BOA PRÁTICA
v_time_i_one := dbms_utility.Get_time();
FOR nx IN 1 .. v_qtd_loop
LOOP
IF v_my_value = 'TESTE 1' THEN
NULL;
ELSIF v_my_value = 'TESTE 2' THEN
NULL;
ELSIF v_my_value = 'TESTE 3' THEN
NULL;
ELSIF v_my_value = 'TESTE 4' THEN
NULL;
ELSIF v_my_value = 'TESTE 5' THEN
NULL;
ELSIF v_my_value = 'TESTE 6' THEN
NULL;
ELSIF v_my_value = 'TESTE 7' THEN
NULL;
ELSIF v_my_value = 'TESTE 8' THEN
NULL;
ELSIF v_my_value = 'TESTE 9' THEN
NULL;
ELSIF v_my_value = 'TESTE 10' THEN
NULL;
END IF;
END LOOP;
v_time_f_one := (dbms_utility.get_time - v_time_i_one)/100;
dbms_output.Put_line('Com boa pratica: '
|| v_time_f_one
|| ' segundos');
–-- SEM BOA PRATICA
v_time_i_two := dbms_utility.get_time();
FOR nx IN 1 .. v_qtd_loop
LOOP
IF v_my_value = 'TESTE 10' THEN
NULL;
ELSIF v_my_value = 'TESTE 9' THEN
NULL;
ELSIF v_my_value = 'TESTE 8' THEN
NULL;
ELSIF v_my_value = 'TESTE 7' THEN
NULL;
ELSIF v_my_value = 'TESTE 6' THEN
NULL;
ELSIF v_my_value = 'TESTE 5' THEN
NULL;
ELSIF v_my_value = 'TESTE 6' THEN
NULL;
ELSIF v_my_value = 'TESTE 7' THEN
NULL;
ELSIF v_my_value = 'TESTE 8' THEN
NULL;
ELSIF v_my_value = 'TESTE 9' THEN
NULL;
END IF;
END LOOP;
v_time_f_two := (dbms_utility.get_time - v_time_i_two)/ 100;
dbms_output.Put_line('Sem boa pratica: '
|| v_time_f_two
|| ' segundos');
dbms_output.Put_line('Ou seja, '
|| Round((v_time_f_two*100)/v_time_f_one,2)
|| '% mais rapido.');
END;
A melhoria da performance entre as duas estruturas é de mais de 800%, já que para a primeira situação o número de testes feitos é bem inferior que a segunda, economizando processamento e passagens desnecessárias.
Para o segundo exemplo, simula-se o que se chama de Short-Circuit. O curto circuito ocorre quando se consegue determinar o resultado de uma validação apenas analisando parte da expressão, sem a necessidade de passagens para os próximos operadores. Conforme, tabela abaixo, quando se quer um resultado TRUE para uma consulta entre TRUE OR FALSE, é necessário verificar a veracidade apenas do lado esquerdo, já que se ele for verdadeiro, não importa o que virá a sua direita, pois a expressão como um todo sempre será verdadeira.
Supondo que temos duas FUNCTION’s onde o processamento de uma leva 0.5 segundos, enquanto a segunda 1 segundo. Na primeira condicional colocaremos ao lado esquerdo a chamada da função que executa em 0.5 segundos e para a parte, sem boa prática, inverteremos a ordem e executaremos antes a função que tem um maior custo e tempo de processamento. Desta maneira, com dissera anteriormente, o simples fato do lado esquerdo ser verdadeiro a validação por completa não precisará ser feito, economizando tempo e processamento.
SET serveroutput ON
DECLARE
v_time_i_one NUMBER;
v_time_i_two NUMBER;
v_time_f_one NUMBER;
v_time_f_two NUMBER;
v_qtd_loop NUMBER := 15;
v_my_value VARCHAR2(10) := 'TESTE 1';
FUNCTION Dorme (p_segundos NUMBER)
RETURN BOOLEAN AS
BEGIN
dbms_lock.Sleep(p_segundos);
RETURN TRUE;
END;
BEGIN
–-- COM BOA PRATICA
v_time_i_one := dbms_utility.get_time();
FOR nx IN 1 .. v_qtd_loop
LOOP
IF Dorme(0.5)
OR
Dorme(1) THEN
NULL;
END IF;
END LOOP;
v_time_f_one := (dbms_utility.get_time - v_time_i_one)/100;
dbms_output.Put_line('Com boa pratica: '
|| v_time_f_one
|| ' segundos');
–-- SEM BOA PRATICA
v_time_i_two := dbms_utility.get_time();
FOR nx IN 1 .. v_qtd_loop
LOOP
IF Dorme(1)
OR
Dorme(0.5) THEN
NULL;
END IF;
END LOOP;
v_time_f_two := (dbms_utility.get_time - v_time_i_two)/ 100;
dbms_output.Put_line('Sem boa pratica: '
|| v_time_f_two
|| ' segundos');
dbms_output.Put_line('Ou seja, '
|| Round((v_time_f_two*100)/v_time_f_one,2)
|| '% mais rapido.');
END;
Nota-se que com a boa prática as validações são executadas na metade do tempo, tenho um ganho de performance considerável.
Através dos exemplos, entende-se que as ordens dos fatores – para estes casos – altera o tempo de resposta. Ainda, fica claro que Tuning PL/SQL nem sempre é utilizar packages, functions ou estruturas “desconhecidas”. As boas práticas são também cruciais para melhor performance.
Referências
Excelente artigo parabéns !