Dev (Back & Front)ARTIGO

Benchmarking de desempenho de código no PHP

Antes de mais nada, Benchmarking é o nome pomposo
dado às práticas adotadas na indústria (não importa qual) que visam
alcançar um desempenho/qualidade superior. Ou seja, Benchmarking é todo tipo de prática cujo objetivo é melhorar/maximizar/amplificar os resultados.

No artigo de hoje vamos abordar benchmarking focado em código,
desempenho especificamente. Não é meu objetivo neste artigo aprofundar
sobre profiling etc. Existem muitas práticas de benchmarking que podem
ser utilizadas para poder alcançar algum tipo de amplificação na sua
área, porém não daria para falarmos detalhadamente de todas em um único
artigo.

Quando desenvolvemos aplicações para Internet e o estresse da
aplicação será muito grande (como um todo), temos que ter cuidado,
muito cuidado. Conhecer mais de uma forma de se implementar uma única
solução pode ser o que vai salvar o seu pescoço.

Benchmarking de código na prática

Muitas vezes, quando estamos meditando para solucionar um problema,
precisamos saber se aquela implementação é rápida. Mas, como assim,
rápida? Como eu vou saber se uma determinada implementação de
código/função é rápida o suficiente de forma que supra as necessidades
sem comprometer a qualidade de modo geral do sistema?

Vale lembrar que o método que vou mostrar aqui faz com que os
resultados variem de máquina para máquina, pois é tudo uma questão de
processamento. Os resultados aqui apresentados são apenas para ILUSTRAR
e SIMULAR para atingirmos resultados significantes.

Este é o seu momento “MythBusters”.

Descobrir qual é a forma mais rápida de se resolver um problema é
uma tarefa simples (nem sempre), em determinados casos, não temos um
parâmetro para saber se devemos seguir pelo caminho A ou B, tudo o que
nos resta é saber várias formas de se codificar/implementar uma
determinada solução e medir o desempenho de todas elas, cada trecho,
para, assim, resolver o problema.

Vou dar um exemplo de como se medir o desempenho de uma
implementação de código, levando em conta um problema que tivemos aqui
no trabalho.

Tivemos que desenvolver um framework adhoc (free style) onde nós
conseguíssemos obter resultados semelhantes a alguns frameworks bem
comuns na Internet como Code Igniter, Cake PHP etc. MVC, Inflector,
ActiveRecord, Rotas de Urls etc.

Agora vem a pergunta na cabeça de vocês:
– Por que re-inventar a roda?

A empresa que pediu para que a gente re-inventasse a roda, alegou
que estes são frameworks não “oficiais”, e são feitos/mantidos por
pessoas em quem eles não confiam (untrusted or non certified
application).

Resumindo? Eles queriam ver como a “coisa” era implementada, para
poder, assim, atingir o melhor desempenho e retirando tudo o que eles
não precisam da aplicação, tornando-a mais leve.

A grande questão?

O aquivo ActiveRecord.class.php está demorando muito para
terminar o seu serviço, quando você instanciava uma nova classe, a dita
cuja tinha um modelo, que, por sua vez, também possuía um controle que
possuía todas as regras do negócio.

Sempre que executássemos um método da classe, por ex ->find,
findFirst ou findAll, ele retornava um array de objetos, ou seja, era um
array e todas as suas posições eram compostas por objetos e suas
respectivas propriedades (atributos).

Exemplo

Array<br />(<br />    [0] => Usuarios Object<br />        (<br />            [nome] => Igor Escobar 1<br />            [email] => blog@igorescobar.com<br />        )<br /><br />    [1] => Usuarios Object<br />        (<br />            [nome] => Igor Escobar 2<br />            [email] => blog@igorescobar.com<br />        )<br /><br />    [2] => Usuarios Object<br />        (<br />            [nome] => Igor Escobar 3<br />            [email] => blog@igorescobar.com<br />        )<br /><br />)

Era mais ou menos assim que ele retornava os usuários de uma
determinada tabela. Este é um array de objetos com apenas 3 elementos,
mas para você ter um resultado para que possamos simular um resultado
no benchmarking mais expressivo, você precisa de um array de objetos
com mais elementos, pois iria exigir mais do processador, etc. No nosso
experimento vou aumentar este array de objetos para 10.000 elementos e
veremos quanto tempo ele demora para fazer o trabalho.

Medindo o desempenho (do trecho)

Criei uma class de exemplo, apenas para conseguirmos a estrutura vista acima:

<?php<br />public class Usuarios {<br />	var $nome;<br />	var $email;<br />}<br />?>

Agora, eu crio a implementação para atingir o resultado esperado:

$arraUsuarios = array();<br />for($i = 1 ; $i <= 10000; $i++):<br />	$obUsuarios = new Usuarios();<br />	$obUsuarios->nome = "Igor Escobar {$i}";<br />	$obUsuarios->email = "blog@igorescobar.com";<br />	$arraUsuarios[] = $obUsuarios;<br />endfor;

Para medir o desempenho exatamente no trecho que queremos, eu utilizo a função microtime() do php.

$time_start = microtime(true);<br />$arraUsuarios = array();<br />for($i = 1 ; $i <= 10000; $i++):<br />	$obUsuarios = new Usuarios();<br />	$obUsuarios->nome = "Igor Escobar {$i}";<br />	$obUsuarios->email = "blog@igorescobar.com";<br />	$arraUsuarios[] = $obUsuarios;<br />endfor;<br />$time_end = microtime(true);<br />$Benchmarking1 = ($time_end - $time_start);<br />echo "Array de Objetos levou: " . $Benchmarking1 . " microsecondos<br />\n";<br />//Output: Array de Objetos levou: 0.04233980178833 microsecondos

Agora vamos criar uma outra implantação, ao invés de retornarmos uma
array de objetos, vamos retornar um array de arrays, ou seja, seria uma
array com n posições e todas as suas propriedades seriam ÍNDICES do
array e não mais atributos do objeto.

Exemplo

Array<br />(<br />    [0] => Array<br />        (<br />            [nome] => Igor Escobar 1<br />            [email] => blog@igorescobar.com<br />        )<br /><br />    [1] => Array<br />        (<br />            [nome] => Igor Escobar 2<br />            [email] => blog@igorescobar.com<br />        )<br /><br />    [2] => Array<br />        (<br />            [nome] => Igor Escobar 3<br />            [email] => blog@igorescobar.com<br />        )<br /><br />)

O mesmo script, apenas montado de maneira diferente (array de arrays), ficou assim:

$time_start = microtime(true);<br />$arraUsuarios = array();<br />for($i = 1 ; $i <= 10000; $i++):<br />	$arraUsuarios[] = array (<br />			'nome' => "Igor Escobar {$i}",<br />			'email' => 'blog@igorescobar.com'<br />		);<br />endfor;<br />$time_end = microtime(true);<br />$Benchmarking2 = ($time_end - $time_start);<br />echo "Array de Arrays levou: " . ($Benchmarking2) . " microsecondos<br />\n";<br />//Output: Array de Arrays levou: 0.036391973495483 microsecondos

Qual método é mais rápido?

// Se resultado for negativo: Método 1 é mais rápido<br />// Se resultado for positivo: Método 2 é mais rápido<br />echo "Resultado: " . ($Benchmarking1 - $Benchmarking2);<br />//Output: Resultado: 0.007580041885376

Viram? Duas formas de fazer a mesma coisa e a segunda forma é 0.007580041885376 microsegundos mais rápida.

Conclusão

Neste simples teste, podemos tirar a seguinte conclusão: neste caso,
utilizar a minha estrutura de retorno como um array de objetos é mais
lento do que trabalhar com um array de arrays. Este é um teste fora da
realidade, quanto mais próximo da realidade a complexidade do
dia-a-dia, este número aumenta, tende a ser cada vez maior.

Veja que este exemplo não envolve conexão com banco de dados,
acrescente todo o estresse que envolve validação de regras de negócio,
segurança, consistência e etc., e veja este número crescer MUITO mais.

Vale lembrar que eu não estou dizendo para vocês não, nunca mais,
utilizarem objetos, porque não é isso, estou apenas ilustrando que,
NESTE CASO, foi mais rápido, e mais interessante para o projeto que se o
retorno desta função fosse um array de arrays, é tudo uma questão de
escolher entre mysql_fetch_assoc, mysql_fetch_array, mysql_fetch_object
ou mysql_fetch_row, cada uma tem suas características e são válidas
dependendo da sua necessidade em questão.

Você pode aplicar isso em “tudo” na sua aplicação, até para saber
quanto tempo uma determinada função demora para terminar seu trabalho
basta utilizar o microtime() no começo e no final da função e subtrair
os valores do maior para o menor.

$time_start = microtime(true);<br />//nome da função<br />$time_end = microtime(true);

E é isso, pessoal, espero que tenham gostado. Certamente, se vocês
utilizarem este recurso para o seu crescimento profissional, será de
muita valia para vocês.

Fonte Completo

<?php<br />class Usuarios {<br />	var $nome;<br />	var $email;<br />}<br /><br />$time_start = microtime(true);<br />$arraUsuarios = array();<br />for($i = 1 ; $i <= 10000; $i++):<br />	$obUsuarios = new Usuarios();<br />	$obUsuarios->nome = "Igor Escobar {$i}";<br />	$obUsuarios->email = "blog@igorescobar.com";<br />	$arraUsuarios[] = $obUsuarios;<br />endfor;<br />$time_end = microtime(true);<br />$Benchmarking1 = ($time_end - $time_start);<br />echo "Array de Objetos levou: " . $Benchmarking1 . " microsecondos<br />\n";<br /><br />$time_start = microtime(true);<br />$arraUsuarios = array();<br />for($i = 1 ; $i <= 10000; $i++):<br />	$arraUsuarios[] = array (<br />			'nome' => "Igor Escobar {$i}",<br />			'email' => 'blog@igorescobar.com'<br />		);<br />endfor;<br />$time_end = microtime(true);<br />$Benchmarking2 = ($time_end - $time_start);<br />echo "Array de Arrays levou: " . ($Benchmarking2) . " microsecondos<br />\n";<br /><br />// Se resultado for negativo: Método 1 é mais rápido<br />// Se resultado for positivo: Método 2 é mais rápido<br />echo "Resultado: " . ($Benchmarking1 - $Benchmarking2);<br /><br />?>

[]’s
Igor.

é formado em Desenvolvimento de Sistemas para Internet pela Faculdade Véris IBTA, Zend PHP 5 Certified Engineer, blogueiro e colunista iMasters, WebInsider e IBM DeveloperWorks.

Ver perfil