รายการฟังก์ชั่น Big-O สำหรับ PHP


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เติบโต ในกรณีที่array_key_existsฟังก์ชั่นถูกนำไปใช้กับการค้นหา hash O (1) ซึ่งจะไม่ทำให้ช้าลงเว้นแต่ว่าตารางแฮชจะได้รับการเติมข้อมูลอย่างมาก (ในกรณีนี้เป็นเพียง O (n))

จนถึงขณะนี้ผมได้มีการค้นพบที่ยิ่งใหญ่-O ผ่านการทดลองและข้อผิดพลาดและบางครั้งมองรหัสที่มา ตอนนี้สำหรับคำถาม ...

มีรายการของ O (หรือปฏิบัติ) ครั้งใหญ่ทางทฤษฎีสำหรับทุก * ฟังก์ชั่น PHP ในตัว?

* หรืออย่างน้อยน่าสนใจ

ตัวอย่างเช่นผมพบว่ามันยากมากที่จะทำนายโอใหญ่ของฟังก์ชั่นที่ระบุไว้เพราะการดำเนินการที่เป็นไปได้ขึ้นอยู่กับโครงสร้างข้อมูลที่ไม่รู้จักหลักของ PHP: array_merge, array_merge_recursive, array_reverse, array_intersect, array_combine, str_replace(กับปัจจัยการผลิตอาร์เรย์) ฯลฯ


31
ปิดหัวข้อทั้งหมด แต่ 1 ไม่สำคัญ
Jason Punyon

24
อาร์เรย์ใน PHP เป็นแฮชเทเบิล นั่นควรบอกทุกสิ่งที่คุณจำเป็นต้องรู้ การค้นหาคีย์ใน hashtable คือ O (1) การค้นหาค่าคือ O (n) - ซึ่งคุณไม่สามารถเอาชนะในชุดที่ไม่ได้เรียงลำดับ ฟังก์ชั่นส่วนใหญ่ที่คุณอยากรู้คือ O (n) แน่นอนถ้าคุณต้องการทราบจริงๆคุณสามารถอ่านแหล่งที่มา: cvs.php.net/viewvc.cgi/php-src/ext/standard/…
Frank Farmer

11
สำหรับบันทึกการดำเนินงานที่เร็วที่สุดของสิ่งที่คุณกำลังพยายามที่จะทำจะ (แทนการใช้โมฆะค่าของคุณ) การใช้งานแล้วสำหรับการทดสอบการแสดงตนใช้true isset($large_prime_array[$number])ถ้าฉันจำได้อย่างถูกต้องมันจะอยู่ในลำดับที่เร็วกว่าin_arrayฟังก์ชั่นหลายร้อยเท่า
mattbasta

3
สัญลักษณ์ Big O ไม่ได้เกี่ยวกับความเร็ว มันเกี่ยวกับการ จำกัด พฤติกรรม
Gumbo

3
@Kendall ฉันไม่ได้เมื่อเทียบกับผมเมื่อเทียบกับarray_key_exists วนซ้ำแต่ละรายการในอาร์เรย์และเปรียบเทียบค่ากับเข็มที่คุณส่งไป หากคุณพลิกค่าเป็นคีย์ (และแทนที่ค่าแต่ละค่าด้วยค่าตัวอย่างเช่นการใช้จะเร็วกว่ามากหลายเท่านี่เป็นเพราะคีย์ของอาร์เรย์ถูกทำดัชนีโดย PHP (เช่น hashtable) ดังนั้นการค้นหา อาเรย์ในลักษณะนี้สามารถปรับปรุงความเร็วได้อย่างมากin_arrayin_arraytrueisset
mattbasta

คำตอบ:


650

เนื่องจากดูเหมือนว่าไม่มีใครทำสิ่งนี้มาก่อนฉันจึงคิดว่าควรมีไว้เพื่อใช้อ้างอิง ฉันเคยไปแล้วและอาจผ่านเกณฑ์มาตรฐานหรือการคัดลอกโค้ดเพื่ออธิบายลักษณะการarray_*ทำงาน ฉันพยายามทำให้ Big-O ที่น่าสนใจใกล้กับด้านบนสุด รายการนี้ไม่สมบูรณ์

หมายเหตุ: Big-O ทั้งหมดที่คำนวณจากการค้นหา hash คือ O (1) แม้ว่าจะเป็น O (n) จริงๆ ค่าสัมประสิทธิ์ของ n นั้นต่ำมากหน่วยความจำ RAM ที่เก็บอาร์เรย์ขนาดใหญ่พอจะทำร้ายคุณก่อนที่ลักษณะของการค้นหา Big-O จะเริ่มมีผล ตัวอย่างเช่นความแตกต่างระหว่างการโทรไปarray_key_existsที่ N = 1 และ N = 1,000,000 คือเพิ่มขึ้น 50%

จุดที่น่าสนใจ :

  1. isset/ array_key_existsเร็วกว่าin_arrayและมากarray_search
  2. +(รวมกัน) นั้นเร็วกว่าarray_merge(และดูดีกว่าเล็กน้อย) แต่มันทำงานแตกต่างกันดังนั้นจำไว้
  3. shuffle อยู่ในระดับเดียวกับ Big-O array_rand
  4. array_pop/ array_pushเร็วกว่าarray_shift/ array_unshiftเนื่องจากปรับบทลงโทษใหม่

การค้นหา :

array_key_existsO (n) แต่จริง ๆ แล้วใกล้กับ O (1) - นี่เป็นเพราะการทำโพลเชิงเส้นในการชน แต่เนื่องจากโอกาสของการชนมีน้อยมากค่าสัมประสิทธิ์ก็น้อยมากเช่นกัน ฉันพบว่าคุณปฏิบัติต่อการค้นหาแบบแฮชในฐานะ O (1) เพื่อให้บิ๊กโอที่สมจริงยิ่งขึ้น ตัวอย่างเช่นความแตกต่างระหว่าง N = 1,000 และ 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 สำหรับทุกคนฉัน)

array_pop O (1)

array_shift O (n) - จะต้องทำดัชนีคีย์ทั้งหมดใหม่อีกครั้ง

array_unshift O (n + ∑ var_i, สำหรับ i ทั้งหมด) - จะต้องทำดัชนีคีย์ทั้งหมดใหม่อีกครั้ง

Array Intersection, Union, การลบ :

array_intersect_key ถ้าการตัดกัน 100% ทำ O (สูงสุด (param_i_size) * ∑param_i_count, สำหรับ i ทั้งหมด), ถ้าแยก 0% ตัด O (∑param_i_size, สำหรับ i ทั้งหมด)

array_intersect ถ้าทางแยก 100% ทำ O (n ^ 2 * ∑param_i_count, สำหรับ i ทั้งหมด), ถ้าทางแยก 0% ตัด O (n ^ 2)

array_intersect_assoc ถ้าการตัดกัน 100% ทำ O (สูงสุด (param_i_size) * ∑param_i_count, สำหรับ i ทั้งหมด), ถ้าแยก 0% ตัด O (∑param_i_size, สำหรับ i ทั้งหมด)

array_diff O (π param_i_size สำหรับทุกคน) - นั่นคือผลงานของ param_sizes ทั้งหมด

array_diff_key O (∑ param_i_size สำหรับ i! = 1) - นี่เป็นเพราะเราไม่จำเป็นต้องวนซ้ำแถวแรก

array_merge O (∑ array_i, i! = 1) - ไม่จำเป็นต้องวนซ้ำในอาร์เรย์แรก

+ (union) O (n) โดยที่ n คือขนาดของอาร์เรย์ที่ 2 (เช่น array_first + array_second) - ค่าใช้จ่ายน้อยกว่า array_merge เนื่องจากไม่ต้องเปลี่ยนหมายเลข

array_replace O (∑ array_i สำหรับทุกคน)

สุ่ม :

shuffle บน)

array_rand O (n) - ต้องการโพลเชิงเส้น

ชัดเจน Big-O :

array_fill บน)

array_fill_keys บน)

range บน)

array_splice O (ออฟเซ็ต + ความยาว)

array_slice O (ออฟเซ็ต + ความยาว) หรือ O (n) ถ้า length = NULL

array_keys บน)

array_values บน)

array_reverse บน)

array_pad O (pad_size)

array_flip บน)

array_sum บน)

array_product บน)

array_reduce บน)

array_filter บน)

array_map บน)

array_chunk บน)

array_combine บน)

ฉันอยากจะขอบคุณEureqa ที่ทำให้การค้นหา Big-O ของฟังก์ชั่นเป็นเรื่องง่าย มันเป็นโปรแกรมฟรีที่น่าทึ่งที่สามารถค้นหาฟังก์ชั่นการฟิตที่ดีที่สุดสำหรับข้อมูลโดยพลการ

แก้ไข:

สำหรับผู้ที่สงสัยว่า 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 ใช้แฮชเทเบิลที่ซ้อนกันสำหรับการชน นั่นคือแทนที่โครงสร้าง logn สำหรับการชนเพียงแค่ใช้ hashtable อื่น และฉันเข้าใจว่าการพูดแฮชเทเบิลของ PHP ที่ใช้งานได้จริงนั้นให้ประสิทธิภาพ O (1) หรืออย่างน้อย O (1) โดยเฉลี่ยนั่นคือสิ่งที่แฮชเทเบิลใช้ ฉันแค่อยากรู้ว่าทำไมคุณถึงพูดว่าพวกเขาเป็น "จริงๆ O (n)" และไม่ใช่ "จริงๆ O (logn)" โพสต์ยอดเยี่ยมโดยวิธี!
Cam

10
ความซับซ้อนของเวลาควรรวมอยู่ในเอกสาร! การเลือกฟังก์ชั่นที่เหมาะสมสามารถช่วยคุณประหยัดเวลาได้มากหรือบอกให้คุณหลีกเลี่ยงการทำสิ่งที่คุณวางแผนไว้: p ขอบคุณสำหรับรายการนี้!
ซามูเอล

41
ฉันรู้ว่ามันเก่า ... แต่อะไรนะ? เส้นโค้งที่ไม่แสดง O (n) ที่ทุกคนก็แสดงให้เห็น O (log n) en.wikipedia.org/wiki/Logarithm ซึ่งมีความแม่นยำกับสิ่งที่คุณคาดหวังสำหรับแผนที่แฮชที่ซ้อนกัน
Andreas

5
Big-O ของ unset บนองค์ประกอบของอาร์เรย์คืออะไร?
Chandrew

12
แม้ว่าแฮชเทเบิ้ลจะมีความซับซ้อนของการค้นหา O (n) ที่เลวร้ายที่สุด แต่ค่าเฉลี่ยคือ O (1) และกรณีเฉพาะของคุณคือการทดสอบเกณฑ์มาตรฐานที่รับประกันว่า 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ฉันไม่ได้ดูที่ internals แต่ฉันค่อนข้างแน่ใจว่าarray_key_existsเป็น O (N) เพราะมันวนซ้ำทุกปุ่มทุกแถวในขณะที่issetพยายามเข้าถึงองค์ประกอบโดยใช้อัลกอริทึมแฮชเดียวกันที่ใช้เมื่อคุณเข้าถึง ดัชนีอาร์เรย์ นั่นควรเป็น 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
นี่เป็นสิ่งที่ผิด ฉันแน่ใจ 100% ว่า array_key_exists ไม่ต้องทำซ้ำในแต่ละคีย์ หากคุณไม่เชื่อลองเข้าไปดูลิงค์ด้านล่างนี้ เหตุผลที่ผู้ออกใช้เร็วกว่ามากนั่นคือมันเป็นโครงสร้างภาษา ซึ่งหมายความว่ามันไม่มีค่าใช้จ่ายในการทำการเรียกใช้ฟังก์ชัน นอกจากนี้ฉันคิดว่ามันอาจแคชการค้นหาด้วยเหตุนี้ นอกจากนี้นี่ไม่ใช่คำตอบสำหรับคำถาม! ฉันต้องการรายการ Big (O) สำหรับฟังก์ชั่น PHP (ตามสถานะคำถาม) ไม่ใช่มาตรฐานเดียวในตัวอย่างของฉัน svn.php.net/repository/php/php-src/branches/PHP_5_3/ext/…
Kendall Hopkins

หากคุณยังไม่เชื่อฉันฉันได้สร้างมาตรฐานเล็ก ๆ เพื่อแสดงให้เห็นถึงประเด็น pastebin.com/BdKpNvkE
Kendall Hopkins

มีอะไรผิดปกติกับมาตรฐานของคุณคือคุณต้องปิดการใช้งาน xdebug =)
Guilherme Blanco

3
มีสองเหตุผลที่สำคัญที่คุณต้องการใช้ isset แทน array_key_exists อย่างแรกคือ isset เป็นภาษาที่ช่วยลดต้นทุนของการเรียกใช้ฟังก์ชัน นี่คล้าย$arrray[] = $appendกับarray_push($array, $append)อาร์กิวเมนต์vs ประการที่สอง array_key_exists ยังแยกความแตกต่างระหว่างค่าที่ไม่ได้ตั้งค่าและค่า null เพราะ$a = array('fred' => null); array_key_exists('fred', $a)จะกลับมาจริงในขณะที่isset($['fred'])จะกลับเท็จ ขั้นตอนพิเศษนี้ไม่สำคัญและจะเพิ่มเวลาดำเนินการอย่างมาก
เพชรฆาต

0

หากผู้คนประสบปัญหาในทางปฏิบัติกับการชนที่สำคัญพวกเขาจะใช้ภาชนะที่มีการค้นหาแฮชรองหรือต้นไม้ที่สมดุล ต้นไม้ที่สมดุลจะให้พฤติกรรมกรณีที่เลวร้ายที่สุดของ O (log n) และ O (1) เฉลี่ย กรณี (แฮชตัวเอง) ค่าใช้จ่ายไม่คุ้มกับการใช้งานจริงในแอปพลิเคชันหน่วยความจำ แต่อาจมีฐานข้อมูลที่ใช้กลยุทธ์แบบผสมนี้เป็นกรณีเริ่มต้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.