Palavra:   

Revista PHP / PHP

Ricardo Martins

Pós graduado em Tecnologia e Sistemas da informação, Ricardo é desenvolvedor de sistemas web e estuda oportunidades que envolvem tecnologia e sistemas de informação. Trabalha com tecnologia desde 1997, e há mais de 5 anos com desenvolvimento. Também escreve o blog do desenvolvedor (http://ricardomartins.info).

Otimizando até 90% loops em arrays utilizando o ponteiro interno de memoria do PHP

Olá a todos novamente.
Hoje vamos falar sobre os métodos de loop em arrays no PHP.
Quando estudava pra certificação da Zend com o livro deles, me deparei com algumas funções que ainda não conhecia (há uns 6 meses atrás).
Achei interessante, e hoje resolvi fazer um teste que me surpreendeu.

Cheguei a obter uma otimização de 91% em um loop de um pequeno array que criei para testes. Tudo bem que o método utilizado para tal conclusão não é o mais seguro, mas se analizarmos bem veremos que a otimização existe.

Bom, vamos aos loops que já conhecemos e os resultados que obtive com o auxílio da função microtime();

Primeiro criei meu array...
PHP
  1. $myArray = array('Brazil','Argentina','Paraguai','Peru','China','Estados Unidos','Japão','Venezuela');
www.revistaphp.com.br

Agora um loop nele com o tradicional foreach()...

PHP
  1. $t1 = microtime();
  2. foreach ($myArray as $key => $value) {
  3. echo $key . "=" . $value ."<br>\r\n";
  4. }
  5. echo "Microtime: " . (microtime() - $t1) . "<br><br>\r\n\r\n";
www.revistaphp.com.br

O microtime obtido foi: 0.00312

Agora um loop com o for()...

PHP
  1. $t1 = microtime();
  2. for($x=0; $x< count($myArray); $x++){
  3. echo $x . "=" . $myArray[$x] . "<br>\r\n";
  4. }
  5. echo "Microtime: " . (microtime() - $t1) . "<br><br>\r\n\r\n";
www.revistaphp.com.br

O microtime com for foi: 0.015497

E finalmente, usando as funções nativas do php sem o auxílio nem criação de nenhuma variável adicional:

PHP
  1. $t1 = microtime();
  2. reset($myArray);
  3. while (key($myArray) !== null) {
  4. echo key($myArray) . "=" . current($myArray) . "<br>\r\n";
  5. next($myArray);
  6. }
  7. echo "Microtime: " . (microtime() - $t1) . "<br><br>\r\n\r\n";
www.revistaphp.com.br

O microtime foi de: 0.001322

Ou seja, 57,62% melhor que o foreach e 91,46% melhor que o for.

Isso ocorre, entre outros motivos, porque ele não usou nenhuma outra variável. No for() nós criamos a variável $x e temos que ficar mudando o valor dela o tempo todo, e chamando ela pra especificar o valor da chave que queremos mostrar na repetição.

No caso do foreach, criamos duas novas variáveis (a $key e a $value).

Entendendo...

Com as funções reset(), key(), current(), e next() nós não criamos nenhuma variável nova, e utilizamos funções nativas do PHP para resgatar os valores do nosso array.

Com elas, o PHP utiliza o ponteiro interno de memória do array, sem a necessidade de criar mais lixo na memória pra fazer um loop.

O reset() como o nome diz, resetou a posição do cursor para o primeiro valor.
A função key() retorna a chave do array na posição atual do ponteiro de memória interno, mas retorna null caso o array tenha chegado ao fim.
O next() como o nome diz, move o ponteiro interno para a próxima posição. Temos também a função prev() que faz o inverso.
A função current() mostrará o valor da posição atual do array.

Enfim, embora o código fique maior e um pouco mais confuso dependendo doa complexidade do array, poderá lhe dar um desempenho muito melhor no caso de arrays gigantes.

Aproveitem a técnica.

Um grande abraço e até a próxima.

Opções de Interação

Comentários

A melhor forma
Por: Tiago, 24/06/2010   11:59:44
Estive a testar todas as formas descritas neste post e todas elas eram muito semelhantes ao nível de tempos de execução.

Mostro em seguida os melhores resultados obtidos:

Foreach: 2.8999999999946E-5
For: 2.1999999999966E-5
Funções Nativas: 2.8999999999946E-5
While: 1.4999999999905E-5


Acho que os resultados falam por si.

Saudações.

Vamos lá...
Por: Ricardo, 22/12/2009   09:05:53
Nesses 18 meses desde que publiquei o post, vi vários comentários e nunca consegui responde-los.
O que me inspirou a escrever este artigo foi o Zend Study Guide (livro de referencia para quem quer tirar a certificação ZCE).
No livro ele explica que o foreach faz uma copia do array, o que significa que quando o array é modificado dentro do loop, ele não é alterado durante a iteração. Por exemplo, quando remove um item do array depois que o loop começou nao fará o loop pular aquele item/elemento.

Aqui vai um pequeno trecho sobre o uso de key():
"The pointer is, in fact, a handyway ofmaintaining the iterative state of an array without
needing an external variable to do the job for us.
The most direct way of manipulating the pointer of an array is by using a series
of functions designed specifically for this purpose. Upon starting an iteration over
an array, the first step is usually to reset the pointer to its initial position using the
reset() function; after that, we can move forward or backwards by one position by
using prev() and next() respectively. At any given point, we can access the value of
the current element using current() and its key using key()."

Com relação ao ultimo comentario do George, é perfeitamente válido no caso do foreach. Na ocasião que escrevi a matéria não havia pensado em fazer deste jeito, mas já vi palestras que mencionavam tal técnica.

Não fiz profilling com os metodos demonstrados, nem o testei com arrays gigantescos, mas acredito que pra quem tiver paciência de manipular diretamente o array, terá uma melhora de performance significativa.

Espero ter esclarecido um pouco mais. Não deixem de comentar.

Obrigado.
Nem tanto...
Por: George, 22/12/2009   08:32:32
Como comentado por outros, o foreach() prevalece em performance e usabilidade em casos maiores. Com relação ao for(), o seu loop realmente não apresenta um resultado satisfatório pois não foi escrito visando a otimização. Repare que, cada vez que o parser verificar a condição de saída do laço ('count($myArray)'), é gerado um processamento totalmente desnecessário, pois este valor não será alterado. Nestes casos, utilize uma variável que tenha como valor o total de elementos do array ($y = count($myArray)). Desta forma o laço será feito através da comparação entre valores inteiros, o que otimizará muito seu benchmark.

George
Faça diferente
Por: William, 30/11/2009   08:34:33
Gostei do teste, mas para uma visão mais ampla eu repeti o seu teste com um array um pouco maior, 133.055 indices, o resultado foi totalmente diferente. O foreach teve um desempenhor melhor, seguido pelo for e por ultimo foi o while. Acho que para grandes quantidades de dados o foreach prevalece
Gostei muito
Por: Gabriel, 24/08/2009   14:39:35
É muito bom saber sobre qualquer coisa que pode nos trazer melhoramentos no desempenho, mesmo que este pareça mínimo em uma aplicação de grande porte pode fazer diferença.
Também não concordo!
Por: Nylson, 15/07/2009   10:31:52
Se você for analizar, não há enorme vantagem em se utilizar o loop dessa forma, já que no seu último exmplo se você analizar melhor, existe key($myArray), o PHP leva um tempo pra calcular isso! OK! Ae depois você REPETE isto! echo key($myArray)."-"...

Nesse caso, como só foi feita uma requisição ao calculo do key do Array dentro do laço, o tempo realmente foi mais vantajoso do que o do foreach, mas como a fução do PHP foreach, já lança os valores de key() e current() em váriaves, em um contexto maior não seria nada legal chamar as funções várias vezes! Já que dificilmente utilizaremos um loop e não trataremos os valores dentro deles, requisitando até mais vezes a função key() dentro do próprio while.

Sou mais o bom e velho foreach!
Não concordo!
Por: Rafael, 04/02/2009   11:44:24
Isso na verdade é facultativo, dependendo do que for requirido no laço, pode ser atribuido pelo foreach, neste caso ficou mais rápido pois você utilizou o loop somente para executar um "Echo" e não utilizou nem o indice, nem os valores mais doque uma vez, isso é melhor com o while, porém se for utilizar os indices ou keys varias vezes dentro deste foreach não precisa declarar as variáveis novamente, e se utilizar as funções de array do PHP mais de uma vez, o seu loop se tornará mais lento.
Muito Bom...
Por: Manoel, 10/06/2008   01:58:44
Apesar de como citado anteriormente os testes de velocidades não serem confiáveis é uma boa analisarmos toda e qualquer vantagem de desempenho em nossos sistemas web, principalmente se tratando de sistemas de empresas que são disponíveis via extranet.