cái gì nhanh hơn: in_array hay Isset? [đóng cửa]


96

Câu hỏi này chỉ dành cho tôi vì tôi luôn thích viết mã được tối ưu hóa có thể chạy trên các máy chủ chậm rẻ (hoặc máy chủ có RẤT NHIỀU lưu lượng truy cập)

Tôi nhìn xung quanh và tôi không thể tìm thấy câu trả lời. Tôi đã tự hỏi điều gì nhanh hơn giữa hai ví dụ đó, hãy nhớ rằng các khóa của mảng trong trường hợp của tôi không quan trọng (mã giả tự nhiên):

<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
    if(!in_array($new_val, $a){
        $a[] = $new_val;
        //do other stuff
    }
}
?>

<?php
$a = array();
while($new_val = 'get over 100k email addresses already lowercased'){
    if(!isset($a[$new_val]){
        $a[$new_val] = true;
        //do other stuff
    }
}
?>

Vì điểm của câu hỏi không phải là xung đột mảng, tôi muốn nói thêm rằng nếu bạn sợ va chạm các chèn cho $a[$new_value], bạn có thể sử dụng $a[md5($new_value)]. nó vẫn có thể gây ra va chạm, nhưng sẽ tránh được một cuộc tấn công DoS có thể xảy ra khi đọc từ tệp do người dùng cung cấp ( http://nikic.github.com/2011/12/28/Supercolliding-a-PHP-array.html )


3
Nếu bạn luôn cố gắng viết mã được tối ưu hóa, chắc chắn bạn đang sử dụng một trình biên dịch sau đó một lần?
mario

59
Tôi bỏ phiếu để mở lại. Câu hỏi được hình thành tốt và câu trả lời được hỗ trợ với các dữ kiện và tài liệu tham khảo. Trong khi tối ưu hóa vi mô , những loại câu hỏi này mang tính xây dựng .
Jason McCreary

5
@JasonMcCreary thứ hai; chỉ một nữa.
Ja͢ck

7
Điều này là nhiều năm sau, nhưng tôi thậm chí sẽ không coi đây là một tối ưu hóa vi mô. Đối với các tập dữ liệu lớn, nó có thể tạo ra rất nhiều sự khác biệt !!
Robert

2
... câu hỏi này có vẻ "mang tính xây dựng" đối với tôi. Tôi sẽ bắt đầu một chiến dịch mở lại khác.
mickmackusa

Câu trả lời:


117

Các câu trả lời cho đến nay là tại chỗ. Sử dụng issettrong trường hợp này nhanh hơn vì

  • Nó sử dụng tìm kiếm băm O (1) trên khóa trong khi in_arrayphải kiểm tra mọi giá trị cho đến khi tìm thấy khớp.
  • Là một opcode, nó có ít chi phí hơn so với việc gọi in_arrayhàm tích hợp.

Điều này có thể được chứng minh bằng cách sử dụng một mảng có các giá trị (10.000 trong thử nghiệm bên dưới), buộc in_arrayphải thực hiện nhiều tìm kiếm hơn.

isset:    0.009623
in_array: 1.738441

Điều này được xây dựng dựa trên điểm chuẩn của Jason bằng cách điền vào một số giá trị ngẫu nhiên và đôi khi tìm một giá trị tồn tại trong mảng. Tất cả đều ngẫu nhiên, vì vậy hãy cẩn thận rằng thời gian sẽ biến động.

$a = array();
for ($i = 0; $i < 10000; ++$i) {
    $v = rand(1, 1000000);
    $a[$v] = $v;
}
echo "Size: ", count($a), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    isset($a[rand(1, 1000000)]);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    in_array(rand(1, 1000000), $a);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

Tôi biết về hàm băm, nhưng tự hỏi tại sao một cái gì đó tương tự không được thực hiện trên các giá trị của mảng khi có thể để tăng tốc các hàm, nó cũng sẽ làm giảm bộ nhớ nếu các giá trị tương tự được sử dụng bằng cách chỉ cần thêm một hàm băm bổ sung vào giá trị .. đúng không?
Fabrizio

3
@Fabrizio - Giá trị mảng có thể được sao chép và chứa các đối tượng không thể băm. Các khóa phải là duy nhất và chỉ có thể là chuỗi và số nguyên để dễ dàng băm chúng. Mặc dù bạn có thể tạo một bản đồ một-một có băm cả khóa và giá trị, nhưng đây không phải là cách mảng của PHP hoạt động.
David Harkness

3
Trong trường hợp bạn chắc chắn rằng mảng của bạn chứa các giá trị duy nhất thì có một tùy chọn khác - flip + Isset .
Arkadij Kuzhel

đáng chú ý là trong ví dụ này, một phiên bản được lật vẫn nhanh hơn so với in_array: `` `$ start = microtime (true); $ foo = array_flip ($ a); for ($ i = 0; $ i <10000; ++ $ i) {Isset ($ foo [rand (1, 1000000)]); } $ total_time = microtime (true) - $ start; echo "Tổng thời gian (bổ sung Isset):", number_format ($ total_time, 6), PHP_EOL;
Andre Baumeier

@AndreBaumeier Cái nào nhanh hơn sẽ phụ thuộc vào kích thước của mảng và số lượng thử nghiệm bạn sẽ thực hiện. Việc lật một mảng mười nghìn phần tử để thực hiện ba bài kiểm tra có lẽ không hiệu quả.
David Harkness

42

Cái nào nhanh hơn: isset()vsin_array()

isset() nhanh hơn.

Mặc dù nó phải rõ ràng, isset()chỉ kiểm tra một giá trị duy nhất. Trong khi đó in_array()sẽ lặp lại trên toàn bộ mảng, kiểm tra giá trị của từng phần tử.

Điểm chuẩn thô khá dễ sử dụng microtime().

Các kết quả:

Total time isset():    0.002857
Total time in_array(): 0.017103

Lưu ý: Kết quả tương tự nhau bất kể tồn tại hay không.

Mã:

<?php
$a = array();
$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    isset($a['key']);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

$start = microtime( true );

for ($i = 0; $i < 10000; ++$i) {
    in_array('key', $a);
}

$total_time = microtime( true ) - $start;
echo "Total time: ", number_format($total_time, 6), PHP_EOL;

exit;

Tài nguyên bổ sung

Tôi khuyến khích bạn cũng nên xem:


Giải pháp tốt. Tôi ngạc nhiên là nhiều người không phân chia thời gian các chức năng / mã của họ nhiều hơn bằng cách sử dụng microtime()hoặc các công cụ khác. Giá trị kinh khủng.
nickhar

1
Việc tìm kiếm một mảng trống cho cùng một khóa chỉ làm nổi bật chi phí của việc gọi in_arrayhàm so với việc sử dụng hàm tích issethợp sẵn. Điều này sẽ tốt hơn với một mảng chứa một loạt các khóa ngẫu nhiên và đôi khi tìm kiếm một khóa / giá trị hiện có.
David Harkness

Tôi sử dụng điểm chuẩn và microtime khá nhiều, nhưng tôi cũng nhận ra rằng, trong khi tôi đang thử nghiệm whileforeachở mỗi lần làm mới, tôi lại nhận được những "người chiến thắng" khác nhau. nó luôn phụ thuộc vào quá nhiều biến máy chủ và tốt nhất là lặp lại một số lượng lớn các lần vào các thời điểm khác nhau và chọn biến chiến thắng thường xuyên hơn hoặc chỉ cần biết những gì đang xảy ra trong nền và biết rằng đó sẽ là người chiến thắng cuối cùng không có vấn đề gì
Fabrizio

@David Harkness, bạn đã chọn đúng câu trả lời của tôi. Nếu bạn muốn nhiều hơn, hãy đứng trên vai tôi và đăng câu trả lời của riêng bạn. :) Tuy nhiên, nếu chi phí của hàm đã đắt hơn đáng kể so với tương đối isset(), thì điều gì khiến bạn nghĩ rằng việc chuyển nó vào một mảng lớn hơn sẽ làm cho nó nhanh hơn ?
Jason McCreary

1
@Fabrizio - Đọc các hàm bămbảng băm .
David Harkness

19

Việc sử dụng isset()tận dụng lợi thế của việc tra cứu nhanh hơn vì nó sử dụng bảng băm , tránh nhu cầu O(n)tìm kiếm.

Đầu tiên, khóa được băm bằng cách sử dụng hàm băm djb để xác định nhóm các khóa được băm tương tự O(1). Sau đó, nhóm sẽ được tìm kiếm lặp đi lặp lại cho đến khi tìm thấy khóa chính xác O(n).

Loại bỏ bất kỳ xung đột băm có chủ ý nào , phương pháp này mang lại hiệu suất tốt hơn nhiều in_array().

Lưu ý rằng khi sử dụng isset()theo cách bạn đã chỉ ra, việc chuyển các giá trị cuối cùng cho một hàm khác yêu cầu sử dụng array_keys()để tạo một mảng mới. Thỏa hiệp bộ nhớ có thể được thực hiện bằng cách lưu trữ dữ liệu trong cả khóa và giá trị.

Cập nhật

Một cách tốt để xem các quyết định thiết kế mã của bạn ảnh hưởng như thế nào đến hiệu suất thời gian chạy, bạn có thể xem phiên bản đã biên dịch của tập lệnh của mình:

echo isset($arr[123])

compiled vars:  !0 = $arr
line     # *  op                           fetch      ext  return  operands
-----------------------------------------------------------------------------
   1     0  >   ZEND_ISSET_ISEMPTY_DIM_OBJ              2000000  ~0      !0, 123
         1      ECHO                                                 ~0
         2    > RETURN                                               null

echo in_array(123, $arr)

compiled vars:  !0 = $arr
line     # *  op                           fetch      ext  return  operands
-----------------------------------------------------------------------------
   1     0  >   SEND_VAL                                             123
         1      SEND_VAR                                             !0
         2      DO_FCALL                                 2  $0      'in_array'
         3      ECHO                                                 $0
         4    > RETURN                                               null

Không chỉ in_array()sử dụng một O(n)tìm kiếm tương đối kém hiệu quả , nó còn cần được gọi là một hàm ( DO_FCALL) trong khi isset()sử dụng một opcode ( ZEND_ISSET_ISEMPTY_DIM_OBJ) cho việc này.


7

Cách thứ hai sẽ nhanh hơn, vì nó chỉ tìm kiếm khóa mảng cụ thể đó và không cần phải lặp lại trên toàn bộ mảng cho đến khi tìm thấy nó (sẽ xem xét mọi phần tử mảng nếu không tìm thấy)


nhưng cũng phụ thuộc của nơi ở của một tìm kiếm var trong phạm vi toàn cầu
el Dude

@ EL2002, bạn có thể vui lòng nói rõ hơn về câu nói đó được không?
Fabrizio,

1
Mike, sẽ không xem xét toàn bộ mảng ngay cả isset()khi nó không được tìm thấy?
Fabrizio,

1
@Fabrizio Không, nó không cần lặp lại. Bên trong (trong C) mảng PHP chỉ là một bảng băm. Để tra cứu một giá trị chỉ mục duy nhất, C chỉ cần tạo một hàm băm của giá trị đó và tra cứu vị trí được gán của nó trong bộ nhớ. Có một giá trị ở đó hoặc không có.
Mike Brant

1
@Fabrizio Bài viết này cung cấp một cái nhìn tổng quan tốt về cách các mảng được biểu diễn bên trong C bằng PHP. nikic.github.com/2012/03/28/...
Mike Brant
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.