Danh sách Big-O cho các hàm PHP


345

Sau khi sử dụng PHP được một lúc, tôi nhận thấy rằng không phải tất cả các hàm PHP tích hợp đều nhanh như mong đợi. Hãy xem xét hai triển khai có thể có của một hàm tìm thấy nếu một số là số nguyên tố bằng cách sử dụng một mảng các số nguyên tố được lưu trữ.

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

Điều này là do in_arrayđược thực hiện với tìm kiếm tuyến tính O (n) sẽ chậm tuyến tính khi $prime_arrayphát triển. Trường hợp array_key_existshàm được thực hiện với tra cứu băm O (1) sẽ không bị chậm lại trừ khi bảng băm trở nên cực kỳ đông dân (trong trường hợp đó chỉ là O (n)).

Cho đến nay tôi đã phải khám phá các big-O thông qua thử nghiệm và lỗi, và thỉnh thoảng nhìn vào mã nguồn . Bây giờ cho câu hỏi ...

Có một danh sách các thời gian O lớn về mặt lý thuyết (hoặc thực tế) cho tất cả * các hàm PHP tích hợp không?

* hoặc ít nhất là những người thú vị

Ví dụ, tôi thấy nó rất khó để dự đoán O lớn các chức năng được liệt kê bởi vì việc thực hiện có thể phụ thuộc vào cấu trúc dữ liệu cốt lõi không rõ của PHP: array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace(với đầu vào mảng) vv


31
Hoàn toàn lạc đề nhưng, 1 không phải là chính.
Jason Punyon

24
Mảng trong PHP là hashtables. Điều đó sẽ cho bạn biết tất cả mọi thứ bạn cần biết. Tìm kiếm một khóa trong hashtable là O (1). Tìm kiếm một giá trị là O (n) - mà bạn không thể đánh bại trên một tập hợp chưa được sắp xếp. Hầu hết các chức năng bạn tò mò có lẽ là O (n). Tất nhiên, nếu bạn thực sự muốn biết, bạn có thể đọc nguồn: cvs.php.net/viewvc.cgi/php-src/ext/st Chuẩn / trộm
Frank Farmer

11
Đối với bản ghi, việc triển khai nhanh nhất những gì bạn đang cố gắng sẽ là (thay vì sử dụng NULL cho các giá trị của bạn) truevà sau đó kiểm tra sự hiện diện bằng cách sử dụng isset($large_prime_array[$number]). Nếu tôi nhớ chính xác, nó sẽ nhanh hơn hàng trăm lần so với in_arraychức năng.
mattbasta

3
Ký hiệu Big O không phải là về tốc độ. Đó là về việc hạn chế hành vi.
Gumbo

3
@Kendall Tôi không so sánh với array_key_exists, tôi đang so sánh với in_array. in_arrayLặp lại từng mục trong mảng và so sánh giá trị với kim mà bạn truyền cho nó. Nếu bạn lật các giá trị sang khóa (và chỉ thay thế từng giá trị bằng giá trị giả như thế true, thì việc sử dụng issetsẽ nhanh hơn nhiều lần. Điều này là do các khóa của một mảng được PHP lập chỉ mục (như hàm băm). một mảng theo cách này có thể có một sự cải thiện đáng kể về tốc độ.
mattbasta 20/03/2016

Câu trả lời:


649

Vì dường như không có ai đã làm điều này trước khi tôi nghĩ rằng nên để nó tham khảo ở đâu đó. Tôi đã đi và thông qua điểm chuẩn hoặc đọc mã để mô tả các array_*chức năng. Tôi đã cố gắng đưa Big-O thú vị hơn lên gần đỉnh. Danh sách này không đầy đủ.

Lưu ý: Tất cả các Big-O được tính toán giả sử tra cứu băm là O (1) mặc dù nó thực sự là O (n). Hệ số của n rất thấp, chi phí ram của việc lưu trữ một mảng đủ lớn sẽ làm bạn tổn thương trước khi các đặc điểm tra cứu Big-O bắt đầu có hiệu lực. Ví dụ: sự khác biệt giữa một cuộc gọi đến array_key_existstại N = 1 và N = 1.000.000 là tăng 50% thời gian.

Điểm thú vị :

  1. isset/ array_key_existsnhanh hơn nhiều so với in_arrayarray_search
  2. +(union) nhanh hơn một chút so với array_merge(và trông đẹp hơn). Nhưng nó hoạt động khác nhau nên hãy ghi nhớ điều đó.
  3. shuffle ở cùng tầng với Big-O như array_rand
  4. array_pop/ array_pushlà nhanh hơn array_shift/ array_unshiftdo hình phạt chỉ số lại

Tra cứu :

array_key_existsO (n) nhưng thực sự gần với O (1) - điều này là do bỏ phiếu tuyến tính trong các va chạm, nhưng vì cơ hội va chạm là rất nhỏ, nên hệ số này cũng rất nhỏ. Tôi thấy bạn coi các tra cứu băm là O (1) để tạo ra một O lớn thực tế hơn. Ví dụ, sự khác nhau giữa N = 1000 và N = 100000 chỉ chậm khoảng 50%.

isset( $array[$index] )O (n) nhưng thực sự gần với O (1) - nó sử dụng tra cứu tương tự như mảng_key_exists. Vì ngôn ngữ của nó được xây dựng, sẽ lưu trữ bộ đệm tra cứu nếu khóa được mã hóa cứng, dẫn đến tăng tốc trong trường hợp sử dụng cùng một khóa.

in_array O (n) - điều này là do nó thực hiện tìm kiếm tuyến tính qua mảng cho đến khi tìm thấy giá trị.

array_search O (n) - nó sử dụng chức năng cốt lõi giống như in_array nhưng trả về giá trị.

Hàm hàng đợi :

array_push O (∑ var_i, cho tất cả i)

array_pop Ô (1)

array_shift O (n) - nó phải reindex tất cả các phím

array_unshift O (n + var_i, với tất cả i) - nó phải reindex tất cả các khóa

Giao lộ mảng, Liên minh, Phép trừ :

array_intersect_key nếu giao lộ 100% làm O (Tối đa (param_i_size) * param_i_count, cho tất cả i), nếu giao lộ 0% giao nhau O (param_i_size, cho tất cả i)

array_intersect nếu giao nhau 100% làm O (n ^ 2 * param_i_count, với tất cả i), nếu giao lộ 0% giao nhau O (n ^ 2)

array_intersect_assoc nếu giao lộ 100% làm O (Tối đa (param_i_size) * param_i_count, cho tất cả i), nếu giao lộ 0% giao nhau O (param_i_size, cho tất cả i)

array_diff O (π param_i_size, cho tất cả i) - Đó là sản phẩm của tất cả các param_sizes

array_diff_key O (∑ param_i_size, for i! = 1) - điều này là do chúng ta không cần lặp lại mảng đầu tiên.

array_merge O (∑ mảng_i, i! = 1) - không cần lặp lại mảng đầu tiên

+ (union) O (n), trong đó n là kích thước của mảng thứ 2 (tức là mảng_first + mảng_second) - ít chi phí hơn mảng_merge vì nó không phải đánh số lại

array_replace O (∑ mảng_i, cho tất cả i)

Ngẫu nhiên :

shuffle Trên)

array_rand O (n) - Yêu cầu một cuộc thăm dò tuyến tính.

Rõ ràng Big-O :

array_fill Trên)

array_fill_keys Trên)

range Trên)

array_splice O (bù + chiều dài)

array_slice O (offset + length) hoặc O (n) nếu length = NULL

array_keys Trên)

array_values Trên)

array_reverse Trên)

array_pad O (pad_size)

array_flip Trên)

array_sum Trên)

array_product Trên)

array_reduce Trên)

array_filter Trên)

array_map Trên)

array_chunk Trên)

array_combine Trên)

Tôi muốn cảm ơn Eureqa vì đã giúp dễ dàng tìm thấy Big-O của các chức năng. Đây là một chương trình miễn phí tuyệt vời có thể tìm thấy chức năng phù hợp nhất cho dữ liệu tùy ý.

BIÊN TẬP:

Đối với những người nghi ngờ rằng việc tìm kiếm mảng PHP là O(N), tôi đã viết một điểm chuẩn để kiểm tra điều đó (chúng vẫn hiệu quả O(1)đối với hầu hết các giá trị thực tế).

Biểu đồ tra cứu mảng 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
@Kendall: Cảm ơn! Tôi đã đọc một chút và hóa ra PHP sử dụng các hàm băm 'lồng nhau' cho các va chạm. Đó là, thay vì một cấu trúc logn cho các va chạm, nó chỉ đơn giản sử dụng một hashtable khác. Và tôi thực sự hiểu rằng các hashtables PHP thực tế nói về hiệu năng O (1) hoặc ít nhất là trung bình O (1) - đó là những gì hashtables dành cho. Tôi chỉ tò mò về lý do tại sao bạn nói họ "thực sự O (n)" chứ không phải "thực sự O (logn)". Great đăng bài bằng cách này!
Cam

10
Sự phức tạp về thời gian nên được đưa vào tài liệu! Chọn đúng chức năng có thể giúp bạn tiết kiệm rất nhiều thời gian, hoặc nói với bạn để tránh làm những gì bạn dự định: p Cảm ơn vì danh sách này rồi!
Samuel

41
Tôi biết điều này đã cũ ... nhưng những gì? Đường cong đó hoàn toàn không hiển thị O (n), nó hiển thị O (log n), en.wikipedia.org/wiki/Logarithm . Điều này cũng chính xác với những gì bạn mong đợi cho các bản đồ băm lồng nhau.
Andreas

5
Big-O của unset trên một phần tử của một mảng là gì?
Chandrew

12
Mặc dù hashtables thực sự có độ phức tạp tra cứu O (n) trong trường hợp xấu nhất, trường hợp trung bình là O (1) và trường hợp cụ thể mà điểm chuẩn của bạn đang kiểm tra thậm chí được đảm bảo O (1), vì nó là một chỉ số số, liên tục, được lập chỉ mục bằng số mảng, sẽ không bao giờ có va chạm băm. Lý do tại sao bạn vẫn thấy sự phụ thuộc vào kích thước mảng không liên quan gì đến độ phức tạp thuật toán, nguyên nhân là do hiệu ứng bộ đệm của CPU. Mảng càng lớn thì càng có nhiều khả năng việc tra cứu truy cập ngẫu nhiên sẽ dẫn đến việc bỏ lỡ bộ đệm (và bộ nhớ cache bị mất cao hơn trong cấu trúc phân cấp).
NikiC

5

Giải thích cho trường hợp bạn mô tả cụ thể là các mảng kết hợp được triển khai dưới dạng bảng băm - vì vậy tra cứu theo khóa (và tương ứng, array_key_exists) là O (1). Tuy nhiên, mảng không được lập chỉ mục theo giá trị, vì vậy cách duy nhất trong trường hợp chung là khám phá xem giá trị có tồn tại trong mảng hay không là tìm kiếm tuyến tính. Không có gì bất ngờ ở đó.

Tôi không nghĩ có tài liệu toàn diện cụ thể về độ phức tạp thuật toán của các phương thức PHP. Tuy nhiên, nếu đó là một mối quan tâm đủ lớn để đảm bảo nỗ lực, bạn luôn có thể xem qua mã nguồn .


Đây không thực sự là một câu trả lời. Như tôi đã nêu trong câu hỏi, tôi đã thử tìm hiểu mã nguồn PHP. Do PHP được triển khai được viết bằng C, sử dụng các macro phức tạp, đôi khi có thể khiến "khó" nhìn thấy O lớn bên dưới cho các hàm.
Kendall Hopkins

@Kendall Tôi bỏ qua tài liệu tham khảo của bạn để đi sâu vào mã nguồn. Tuy nhiên, có một câu trả lời trong câu trả lời của tôi: "Tôi không nghĩ có tài liệu toàn diện cụ thể về độ phức tạp thuật toán của các phương thức PHP." "Không" là một câu trả lời hoàn toàn hợp lệ. (c:
Dathan

4

Bạn hầu như luôn muốn sử dụng issetthay vì array_key_exists. Tôi không nhìn vào phần bên trong, nhưng tôi khá chắc chắn đó array_key_existslà O (N) vì nó lặp lại qua từng phím của mảng, trong khi issetcố gắng truy cập phần tử bằng thuật toán băm tương tự được sử dụng khi bạn truy cập một chỉ số mảng. Đó phải là O (1).

Một "gotcha" cần chú ý là:

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

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

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

Tôi tò mò, vì vậy tôi đã điểm chuẩn sự khác biệt:

<?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 giây
array_key_exists:2.33202195168 giây

Tất nhiên, điều này không thể hiện sự phức tạp về thời gian, nhưng nó cho thấy 2 chức năng so sánh với nhau như thế nào.

Để kiểm tra độ phức tạp của thời gian, hãy so sánh lượng thời gian cần thiết để chạy một trong các chức năng này trên khóa đầu tiên và khóa cuối cùng.


9
Cái này sai. Tôi chắc chắn 100% mảng_key_exists không phải lặp lại qua từng khóa. Nếu bạn không tin hãy xem liên kết dưới đây. Lý do ngay lập tức nhanh hơn nhiều là vì nó là một cấu trúc ngôn ngữ. Điều đó có nghĩa là nó không có chi phí thực hiện cuộc gọi chức năng. Ngoài ra, tôi nghĩ rằng nó có thể được lưu vào bộ đệm, vì điều này. Ngoài ra, đây không phải là một câu trả lời cho CÂU HỎI! Tôi muốn một danh sách Big (O) cho các hàm PHP (như trạng thái câu hỏi). Không một điểm chuẩn nào trong các ví dụ của tôi. svn.php.net/reposeective/php/php-src/branches/PHP_5_3/ext/iêu
Kendall Hopkins

Nếu bạn vẫn không tin tôi, tôi đã tạo một điểm chuẩn nhỏ để chứng minh luận điểm. pastebin.com/BdKpNvkE
Kendall Hopkins

Điều sai với điểm chuẩn của bạn là bạn phải tắt xdebug. =)
Guilherme Blanco

3
Có hai lý do quan trọng khiến bạn muốn sử dụng ngay lập tức trên mảng_key_exists. Đầu tiên, isset là một ngôn ngữ xây dựng giảm thiểu chi phí của một cuộc gọi chức năng. Điều này giống như đối số $arrray[] = $appendvs. array_push($array, $append)Thứ hai, Array_key_exists cũng phân biệt giữa các giá trị không được đặt và null. Vì $a = array('fred' => null); array_key_exists('fred', $a)sẽ trả về true trong khi isset($['fred'])sẽ trả về false. Bước bổ sung này là không tầm thường và sẽ làm tăng đáng kể thời gian thực hiện.
orca

0

Nếu mọi người gặp rắc rối trong thực tế với các va chạm quan trọng, họ sẽ triển khai các thùng chứa với một tra cứu băm thứ cấp hoặc cây cân bằng. Cây cân bằng sẽ cho O (log n) hành vi trường hợp xấu nhất và O (1) avg. trường hợp (băm chính nó). Chi phí hoạt động không đáng giá trong hầu hết các ứng dụng bộ nhớ, nhưng có lẽ có những cơ sở dữ liệu thực hiện hình thức chiến lược hỗn hợp này như trường hợp mặc định của chúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.