Depois de usar o PHP há algum tempo, notei que nem todas as funções embutidas do PHP são tão rápidas quanto o esperado. Considere estas duas implementações possíveis de uma função que descobre se um número é primo usando uma matriz de números primos em cache.
//very slow for large $prime_array
$prime_array = array( 2, 3, 5, 7, 11, 13, .... 104729, ... );
$result_array = array();
foreach( $prime_array => $number ) {
$result_array[$number] = in_array( $number, $large_prime_array );
}
//speed is much less dependent on size of $prime_array, and runs much faster.
$prime_array => array( 2 => NULL, 3 => NULL, 5 => NULL, 7 => NULL,
11 => NULL, 13 => NULL, .... 104729 => NULL, ... );
foreach( $prime_array => $number ) {
$result_array[$number] = array_key_exists( $number, $large_prime_array );
}
Isso ocorre porque in_array
é implementado com uma pesquisa linear O (n) que diminui linearmente à medida que $prime_array
cresce. Onde a array_key_exists
função é implementada com uma pesquisa de hash O (1), que não diminui a velocidade, a menos que a tabela de hash seja extremamente preenchida (nesse caso, é apenas O (n)).
Até agora, tive que descobrir os grandes Os por tentativa e erro, e ocasionalmente olhando o código-fonte . Agora, a pergunta ...
Existe uma lista dos grandes tempos teóricos (ou práticos) para todas as * funções embutidas do PHP?
* ou pelo menos os interessantes
Por exemplo, acho que é muito difícil prever o grande O de funções listadas porque a possível implementação depende de estruturas de dados do núcleo desconhecido do PHP: array_merge
, array_merge_recursive
, array_reverse
, array_intersect
, array_combine
,str_replace
(com entradas de matriz), etc.
true
e, em seguida, testar a presençaisset($large_prime_array[$number])
. Se bem me lembro, é da ordem de centenas de vezes mais rápido que ain_array
função.array_key_exists
, estou comparando ain_array
.in_array
itera cada item da matriz e compara o valor à agulha que você passa para ela. Se você alternar os valores para a chave (e apenas substituir cada um deles por um valor fictício comotrue
, usarisset
é muitas vezes mais rápido. Isso ocorre porque as chaves de uma matriz são indexadas pelo PHP (como uma hashtable)). uma matriz dessa maneira pode ter uma melhoria significativa na velocidade. #Respostas:
Como não parece que alguém tenha feito isso antes, pensei que seria uma boa ideia tê-lo como referência em algum lugar. Eu fui embora e via benchmark ou skimming de código para caracterizar o
array_*
funções. Eu tentei colocar o Big-O mais interessante perto do topo. Está lista não está completa.Nota: Todo o Big-O foi calculado assumindo uma pesquisa de hash é O (1), embora seja realmente O (n). O coeficiente de n é tão baixo que a sobrecarga de memória RAM de armazenar uma matriz grande o suficiente o prejudicaria antes que as características da pesquisa Big-O começassem a surtir efeito. Por exemplo, a diferença entre uma chamada para
array_key_exists
N = 1 e N = 1.000.000 é de aproximadamente 50% de aumento de tempo.Pontos interessantes :
isset
/array_key_exists
é muito mais rápido quein_array
earray_search
+
(união) é um pouco mais rápido quearray_merge
(e parece melhor). Mas funciona de maneira diferente, portanto, lembre-se disso.shuffle
está no mesmo nível Big-O quearray_rand
array_pop
/array_push
é mais rápido quearray_shift
/array_unshift
devido à re-indexação da penalidadePesquisas :
array_key_exists
O (n), mas realmente próximo de O (1) - isso ocorre por causa de pesquisas lineares em colisões, mas como a chance de colisões é muito pequena, o coeficiente também é muito pequeno. Acho que você trata as pesquisas de hash como O (1) para fornecer um big-O mais realista. Por exemplo, a diferença entre N = 1000 e N = 100000 é apenas cerca de 50% mais lenta.isset( $array[$index] )
O (n), mas realmente próximo de O (1) - ele usa a mesma pesquisa que array_key_exists. Como é uma construção de idioma, a pesquisa será armazenada em cache se a chave for codificada, resultando em aceleração nos casos em que a mesma chave for usada repetidamente.in_array
O (n) - isso ocorre porque ele faz uma pesquisa linear pela matriz até encontrar o valor.array_search
O (n) - usa a mesma função principal que in_array, mas retorna valor.Funções de fila :
array_push
O (∑ var_i, para todos os i)array_pop
O (1)array_shift
O (n) - ele precisa reindexar todas as chavesarray_unshift
O (n + ∑ var_i, para todos os i) - ele precisa reindexar todas as chavesIntersecção, União, Subtração da Matriz :
array_intersect_key
se a interseção 100% fizer O (Max (param_i_size) * ∑param_i_count, para todos os i), se a interseção 0% interceptar O (∑param_i_size, para todos os i)array_intersect
se a interseção 100% fizer O (n ^ 2 * amparam_i_count, para todos os i), se a interseção 0% interceptar O (n ^ 2)array_intersect_assoc
se a interseção 100% fizer O (Max (param_i_size) * ∑param_i_count, para todos os i), se a interseção 0% interceptar O (∑param_i_size, para todos os i)array_diff
O (π param_i_size, para todos os i) - Esse é o produto de todos os param_sizesarray_diff_key
O (∑ param_i_size, para i! = 1) - isso ocorre porque não precisamos iterar sobre a primeira matriz.array_merge
O (∑ matriz_i, i! = 1) - não precisa iterar sobre a primeira matriz+
(union) O (n), onde n é o tamanho da segunda matriz (ou seja, array_first + array_second) - menos sobrecarga que array_merge, pois não precisa renumerararray_replace
O (∑ matriz_i, para todos os i)Aleatório :
shuffle
Em)array_rand
O (n) - Requer uma pesquisa linear.Big-O óbvio :
array_fill
Em)array_fill_keys
Em)range
Em)array_splice
O (deslocamento + comprimento)array_slice
O (deslocamento + comprimento) ou O (n) se comprimento = NULLarray_keys
Em)array_values
Em)array_reverse
Em)array_pad
O (tamanho do bloco)array_flip
Em)array_sum
Em)array_product
Em)array_reduce
Em)array_filter
Em)array_map
Em)array_chunk
Em)array_combine
Em)Gostaria de agradecer à Eureqa por facilitar a localização do Big-O das funções. É um programa gratuito incrível que pode encontrar a melhor função de ajuste para dados arbitrários.
EDITAR:
Para aqueles que duvidam que as pesquisas de matriz PHP sejam
O(N)
, escrevi uma referência para testar isso (elas ainda são efetivamenteO(1)
para os valores mais realistas).fonte
A explicação para o caso que você descreve especificamente é que matrizes associativas são implementadas como tabelas de hash - portanto, procure por chave (e, correspondentemente,
array_key_exists
) é O (1). No entanto, matrizes não são indexadas por valor, portanto, a única maneira de descobrir se existe um valor na matriz é uma pesquisa linear. Não há nenhuma surpresa lá.Eu não acho que exista documentação abrangente específica da complexidade algorítmica dos métodos PHP. No entanto, se é uma preocupação grande o suficiente para justificar o esforço, você sempre pode procurar no código-fonte .
fonte
Você quase sempre deseja usar em
isset
vez dearray_key_exists
. Não estou olhando para os internos, mas tenho certeza de quearray_key_exists
é O (N) porque itera sobre todas as chaves da matriz, enquantoisset
tenta acessar o elemento usando o mesmo algoritmo de hash usado quando você acessa um índice de matriz. Isso deve ser O (1).Um "truque" a ser observado é o seguinte:
Fiquei curioso, por isso comparei a diferença:
is_set:
0.132308959961 segundosarray_key_exists:
2.33202195168 segundosObviamente, isso não mostra a complexidade do tempo, mas mostra como as duas funções se comparam.
Para testar a complexidade do tempo, compare a quantidade de tempo necessária para executar uma dessas funções na primeira e na última chave.
fonte
$arrray[] = $append
vs.array_push($array, $append)
Segundo, array_key_exists também diferencia entre valores não definidos e nulos. Para$a = array('fred' => null);
array_key_exists('fred', $a)
retornará verdadeiro, enquantoisset($['fred'])
retornará falso. Esta etapa extra não é trivial e aumentará bastante o tempo de execução.Se as pessoas estivessem enfrentando problemas na prática com colisões importantes, implementariam contêineres com uma pesquisa secundária de hash ou uma árvore equilibrada. A árvore balanceada daria O (log n) ao pior caso e O (1) avg. case (o próprio hash). A sobrecarga não vale a pena na maioria das aplicações práticas em memória, mas talvez haja bancos de dados que implementem essa forma de estratégia mista como seu caso padrão.
fonte