PHP 용 Big-O 함수 목록


346

잠시 동안 PHP를 사용한 후, 모든 내장 PHP 기능이 예상대로 빠르지는 않습니다. 캐시 된 소수 배열을 사용하여 숫자가 소수인지 찾는 함수의이 두 가지 가능한 구현을 고려하십시오.

//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 );
}

이것은 in_array선형 검색 O (n)으로 구현 되기 때문에 $prime_array증가 함에 따라 선형으로 느려질 것 입니다. 를 Where array_key_exists기능이 해시 테이블이 매우 채워 도착하지 않는 한 진정되지 않습니다 해시 조회 O (1)로 구현된다 (이 경우는 유일 O (N)).

지금까지 시행 착오를 통해 big-O를 발견하고 때로는 소스 코드를 살펴 보았습니다 . 질문은 ...

내장 된 PHP 함수 모두에 대한 이론적 (또는 실제적인) 큰 O 시간 목록이 있습니까?

또는 적어도 흥미로운 것

: 가능한 구현은 PHP의 알 수없는 핵심 데이터 구조에 의존하기 때문에 예를 들어, 나는 나열된 기능의 큰 O를 예측하는 것은 매우 어려운 그것을 발견 array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace(배열 입력 포함) 등


31
완전히 벗어난 주제이지만 1은 소수가 아닙니다.
Jason Punyon

24
PHP의 배열은 해시 테이블입니다. 그것은 당신이 알아야 할 모든 것을 말해 줄 것입니다. 해시 테이블에서 키를 검색하는 것은 O (1)입니다. 값을 검색하는 것은 O (n)입니다. 분류되지 않은 세트에서는 이길 수 없습니다. 궁금한 대부분의 함수는 아마도 O (n) 일 것입니다. 물론, 당신이 정말로 알고 싶다면, 당신은 소스를 읽을 수 있습니다 : cvs.php.net/viewvc.cgi/php-src/ext/standard/…
Frank Farmer

11
기록을 위해, 당신이하려고하는 가장 빠른 구현은 (값에 NULL을 사용하는 대신)을 사용 true하고 존재를 테스트하는 것 isset($large_prime_array[$number])입니다. 올바르게 기억하면 in_array함수 보다 수백 배 빠릅니다 .
mattbasta

3
Big O 표기법은 속도에 관한 것이 아닙니다. 행동 제한에 관한 것입니다.
Gumbo

3
@Kendall 나는 비교하고 있지 않다 . array_key_exists나는 비교하고있다 in_array. in_array배열의 각 항목을 반복하고 값을 전달한 바늘과 비교합니다. 값을 키로 넘기고 각 값을 같은 더미 값으로 바꾸면 true사용 isset속도가 여러 배 더 빠릅니다. 배열의 키는 PHP에서 해시 테이블과 같이 색인을 생성하기 때문입니다. 이런 식으로 배열은 속도를 크게 향상시킬 수 있습니다.
mattbasta

답변:


650

어딘가에 이것을 참조하는 것이 좋은 생각이라고 생각하기 전에 아무도 이것을 한 것처럼 보이지 않습니다. 나는 array_*기능 을 특성화하기 위해 벤치 마크 또는 코드 스키밍을 통해 갔다 . 더 흥미로운 Big-O를 맨 위에 두려고했습니다. 이 목록은 완전하지 않습니다.

참고 : 해시 조회를 가정하여 계산 된 모든 Big-O는 실제로 O (n)이지만 O (1)입니다. n의 계수가 너무 낮기 때문에 충분히 큰 배열을 저장하는 램 오버 헤드로 인해 Big-O 조회 특성이 적용되기 전에 피해를 입을 수 있습니다. 예를 들어 array_key_existsN = 1과 N = 1,000,000에서 의 통화 차이 는 ~ 50 % 시간 증가입니다.

재미있는 포인트 :

  1. isset/ array_key_exists훨씬보다 빨리 in_arrayarray_search
  2. +(연합)보다 약간 빠르며 array_merge멋지게 보입니다. 그러나 다르게 작동하므로 명심하십시오.
  3. shuffle 같은 Big-O 계층에 있습니다 array_rand
  4. array_pop/ 재 인덱스 페널티로 인해 / array_push보다 빠릅니다array_shiftarray_unshift

조회 :

array_key_existsO (n)이지만 실제로 O (1)에 가깝습니다. 이것은 충돌의 선형 폴링 때문이지만 충돌 가능성이 매우 작기 때문에 계수도 매우 작습니다. 보다 현실적인 big-O를 제공하기 위해 해시 조회를 O (1)로 취급합니다. 예를 들어 N = 1000과 N = 100000의 차이는 약 50 % 만 느려집니다.

isset( $array[$index] )O (n)이지만 실제로 O (1)에 가깝습니다. array_key_exists와 동일한 조회를 사용합니다. 언어 구성이므로 키를 하드 코딩하면 조회를 캐시하여 동일한 키를 반복적으로 사용하는 경우 속도를 높입니다.

in_array O (n)-값을 찾을 때까지 배열을 통해 선형 검색을 수행하기 때문입니다.

array_search O (n)-in_array와 동일한 핵심 함수를 사용하지만 값을 반환합니다.

대기열 기능 :

array_push O (∑ var_i, 모든 i)

array_pop O (1)

array_shift O (n)-모든 키를 다시 색인화해야합니다

array_unshift O (n + ∑ var_i, 모든 i)-모든 키를 다시 색인화해야합니다

배열 교차, 조합, 빼기 :

array_intersect_key 교차로 100 %가 O를 수행하는 경우 (모든 i에 대해 최대 (param_i_size) * ∑param_i_count), 교차로 0 %가 O를 교차하면 (i, 모두 i에 대해 param_i_size)

array_intersect 교차로 100 %가 O (n ^ 2 * ∑param_i_count, 모든 i)를 수행하는 경우 교차로 0 %가 O (n ^ 2)를 교차하는 경우

array_intersect_assoc 교차로 100 %가 O를 수행하는 경우 (모든 i에 대해 최대 (param_i_size) * ∑param_i_count), 교차로 0 %가 O를 교차하면 (i, 모두 i에 대해 param_i_size)

array_diff O (π param_i_size, all i)-모든 param_size의 곱입니다

array_diff_key O (∑ param_i_size, for i! = 1)-첫 번째 배열을 반복 할 필요가 없기 때문입니다.

array_merge O (∑ array_i, i! = 1)-첫 번째 배열을 반복 할 필요가 없습니다.

+ (union) O (n), 여기서 n은 두 번째 배열의 크기입니다 (즉, array_first + array_second)-번호를 다시 매길 필요가 없기 때문에 array_merge보다 오버 헤드가 적습니다.

array_replace O (∑ array_i, 모든 i)

무작위 :

shuffle 의 위에)

array_rand O (n)-선형 폴링이 필요합니다.

명백한 Big-O :

array_fill 의 위에)

array_fill_keys 의 위에)

range 의 위에)

array_splice O (오프셋 + 길이)

array_slice 길이가 NULL 인 경우 O (오프셋 + 길이) 또는 O (n)

array_keys 의 위에)

array_values 의 위에)

array_reverse 의 위에)

array_pad O (패드 _ 크기)

array_flip 의 위에)

array_sum 의 위에)

array_product 의 위에)

array_reduce 의 위에)

array_filter 의 위에)

array_map 의 위에)

array_chunk 의 위에)

array_combine 의 위에)

함수의 Big-O를 쉽게 찾을 수있게 해주신 Eureqa 에게 감사드립니다 . 임의의 데이터에 가장 적합한 기능을 찾을 수 있는 놀라운 무료 프로그램입니다.

편집하다:

PHP 배열 조회가 의심된다는 사람들을 위해 O(N), 나는 그것을 테스트하기 위해 벤치 마크를 작성했습니다 (그들은 여전히 O(1)가장 현실적인 값에 효과적 입니다).

PHP 배열 조회 그래프

$tests = 1000000;
$max = 5000001;


for( $i = 1; $i <= $max; $i += 10000 ) {
    //create lookup array
    $array = array_fill( 0, $i, NULL );

    //build test indexes
    $test_indexes = array();
    for( $j = 0; $j < $tests; $j++ ) {
        $test_indexes[] = rand( 0, $i-1 );
    }

    //benchmark array lookups
    $start = microtime( TRUE );
    foreach( $test_indexes as $test_index ) {
        $value = $array[ $test_index ];
        unset( $value );
    }
    $stop = microtime( TRUE );
    unset( $array, $test_indexes, $test_index );

    printf( "%d,%1.15f\n", $i, $stop - $start ); //time per 1mil lookups
    unset( $stop, $start );
}

5
@ 켄달 : 감사합니다! 나는 약간의 독서를했고 PHP는 충돌에 '중첩'해시 테이블을 사용합니다. 즉, 충돌에 대한 로그온 구조 대신 단순히 다른 해시 테이블을 사용합니다. 그리고 실제로 말하는 PHP 해시 테이블은 O (1) 성능 또는 평균 O (1) 이상을 제공한다는 것을 이해합니다. 이것이 해시 테이블의 목적입니다. 왜 당신이 "정말 O (logn)"이 아니라 "정말 O (n)"라고 말했는지 궁금합니다. 그건 그렇고 멋진 게시물!
Cam

10
문서에 시간 복잡성이 포함되어야합니다! 올바른 기능을 선택하면 많은 시간을 절약 할 수 있고 계획 한 작업을 수행하지 않도록 할 수 있습니다.
사무엘

41
나는 이것이 오래되었다는 것을 알고있다. 그러나 무엇? 이 곡선에는 O (n)이 전혀 표시되지 않으며 O (log n), en.wikipedia.org/wiki/Logarithm이 표시 됩니다. 중첩 된 해시 맵에 대해 예상 할 때도 정확합니다.
Andreas

5
배열의 요소에서 설정되지 않은 Big-O는 무엇입니까?
Chandrew

12
해시 테이블에는 실제로 최악의 O (n) 조회 복잡성이 있지만 평균 사례는 O (1)이며 벤치 마크가 테스트되는 특정 사례 는 0부터 시작하여 연속적으로 숫자로 인덱스 되므로 O (1) 까지 보장 됩니다. 해시 충돌이 발생하지 않는 배열. 배열 크기에 여전히 의존하는 이유는 알고리즘 복잡성과 아무 관련이 없으며 CPU 캐시 효과 때문입니다. 어레이가 클수록 랜덤 액세스 조회로 인해 캐시 누락이 발생할 가능성이 높습니다 (계층 구조에서 캐시 누락이 더 높음).
NikiC

5

구체적으로 설명하는 경우에 대한 설명은 연관 배열이 해시 테이블로 구현된다는 것입니다. 따라서 키 (및 이에 따라 array_key_exists)에 의한 조회 는 O (1)입니다. 그러나 배열은 값으로 색인화되지 않으므로 일반적으로 배열에 값이 있는지 여부를 발견하는 유일한 방법은 선형 검색입니다. 놀랄 일이 없습니다.

PHP 메소드의 알고리즘 복잡성에 대한 포괄적 인 문서가 있다고 생각하지 않습니다. 그러나 노력을 보증하기에 충분히 큰 우려가 있다면 언제든지 소스 코드를 살펴볼 수 있습니다 .


이것은 실제로 답이 아닙니다. 질문에서 언급했듯이 이미 PHP 소스 코드를 살펴 보았습니다. PHP는 복잡한 매크로를 사용하여 C로 작성되기 때문에 때때로 함수의 기본 O를 "보기"어렵게 만들 수 있습니다.
Kendall Hopkins

@ 켄달 나는 소스 코드에 다이빙에 대한 귀하의 참조를 간과했습니다. 그러나 답장에는 "PHP 메소드의 알고리즘 복잡성에 대한 구체적인 포괄적 인 문서가 없다고 생각합니다." "아니오"는 완벽하게 유효한 답변입니다. (c :
Dathan

4

거의 항상 isset대신에 사용하고 싶습니다 array_key_exists. 내부를보고 있지는 않지만 array_key_exists배열의 모든 키를 반복하면서 isset액세스 할 때 사용되는 것과 동일한 해시 알고리즘을 사용하여 요소에 액세스하려고하기 때문에 O (N)입니다. 배열 인덱스 O (1)이어야합니다.

주의해야 할 "gotcha"는 다음과 같습니다.

$search_array = array('first' => null, 'second' => 4);

// returns false
isset($search_array['first']);

// returns true
array_key_exists('first', $search_array);

궁금해서 그 차이를 벤치마킹했습니다.

<?php

$bigArray = range(1,100000);

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    isset($bigArray[50000]);
}

echo 'is_set:', microtime(true) - $start, ' seconds', '<br>';

$iterations = 1000000;
$start = microtime(true);
while ($iterations--)
{
    array_key_exists(50000, $bigArray);
}

echo 'array_key_exists:', microtime(true) - $start, ' seconds';
?>

is_set:0.132308959961 초
array_key_exists:2.33202195168 초

물론 이것은 시간의 복잡성을 보여주지는 않지만 두 기능이 서로 어떻게 비교되는지 보여줍니다.

시간 복잡성을 테스트하려면 첫 번째 키와 마지막 키에서 이러한 기능 중 하나를 실행하는 데 걸리는 시간을 비교하십시오.


9
이것은 잘못이다. array_key_exists가 각 키를 반복 할 필요가 없다는 것을 100 % 확신합니다. 당신이 믿지 않는 경우 아래 링크를 살펴보십시오. 너무 빠른 이유는 언어 구성이기 때문입니다. 이는 함수 호출의 오버 헤드가 없다는 것을 의미합니다. 또한, 이것이 때문에 조회를 캐싱 할 수 있다고 생각합니다. 또한 이것은 질문에 대한 답변이 아닙니다! PHP 함수에 대한 Big (O)의 목록을 원합니다 (질문으로). 내 예제의 단일 벤치 마크가 아닙니다. svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/…
Kendall Hopkins

여전히 믿지 않는다면 요점을 설명하기 위해 작은 벤치 마크를 만들었습니다. pastebin.com/BdKpNvkE
Kendall Hopkins

벤치 마크에서 잘못된 점은 xdebug를 비활성화해야한다는 것입니다. =)
길 레르 메 블랑코

3
array_key_exists보다 isset을 사용하는 데는 두 가지 중요한 이유가 있습니다. 첫째, isset은 함수 호출 비용을 완화하는 언어 구성입니다. 이것은 $arrray[] = $appendvs array_push($array, $append)인수 와 유사합니다 . 둘째, array_key_exists는 설정되지 않은 값과 널값을 구분합니다. for $a = array('fred' => null); array_key_exists('fred', $a)는 true를 isset($['fred'])반환하고 false는 반환합니다. 이 추가 단계는 사소하지 않으며 실행 시간이 크게 증가합니다.
orca

0

사람들이 키 충돌로 실제로 문제가 발생하면 보조 해시 조회 또는 균형 트리를 사용하여 컨테이너를 구현합니다. 균형 트리는 O (log n) 최악의 동작과 O (1) 평균을 제공합니다. 사례 (해시 자체). 오버 헤드는 메모리 응용 프로그램에서 가장 실용적이지 않지만 이러한 형식의 혼합 전략을 기본 사례로 구현하는 데이터베이스가있을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.