อะไรเร็วกว่า: in_array หรือ isset [ปิด]


99

คำถามนี้เป็นเพียงสำหรับฉันเพราะฉันมักจะชอบเขียนโค้ดที่ปรับให้เหมาะสมซึ่งสามารถทำงานบนเซิร์ฟเวอร์ที่ช้าราคาถูก (หรือเซิร์ฟเวอร์ที่มีปริมาณการใช้งานมาก)

ฉันมองไปรอบ ๆ และไม่สามารถหาคำตอบได้ ฉันสงสัยว่าอะไรจะเร็วกว่าระหว่างสองตัวอย่างนี้โปรดทราบว่าคีย์ของอาร์เรย์ในกรณีของฉันไม่สำคัญ (รหัสหลอกตามธรรมชาติ):

<?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
    }
}
?>

เป็นจุดของคำถามที่ไม่ได้ชนอาร์เรย์ที่ฉันต้องการที่จะเพิ่มว่าถ้าคุณกลัวการชนแทรกสำหรับคุณสามารถใช้$a[$new_value] $a[md5($new_value)]มันยังคงทำให้เกิดการชนกันได้ แต่จะหลีกเลี่ยงการโจมตี DoS ที่เป็นไปได้เมื่ออ่านจากไฟล์ที่ผู้ใช้ให้มา ( http://nikic.github.com/2011/12/28/Supercolliding-a-PHP-array.html )


3
หากคุณพยายามที่จะเขียนโค้ดที่ดีที่สุดอยู่เสมอคุณจะต้องใช้ profiler เป็นประจำอยู่แล้วใช่หรือไม่?
มาริโอ

61
ฉันโหวตให้เปิดอีกครั้ง คำถามมีรูปแบบที่ดีและคำตอบได้รับการสนับสนุนพร้อมข้อเท็จจริงและการอ้างอิง ในขณะที่ไมโครเพิ่มประสิทธิภาพประเภทของคำถามเหล่านี้มีความสร้างสรรค์
Jason McCreary

5
@JasonMcCreary วินาที; อีกเพียงหนึ่ง
Ja͢ck

7
นี่เป็นเวลาหลายปีต่อมา แต่ฉันจะไม่คิดว่านี่เป็นการเพิ่มประสิทธิภาพแบบไมโครด้วยซ้ำ สำหรับชุดข้อมูลขนาดใหญ่สามารถสร้างความแตกต่างได้มากมาย !!
Robert

2
... คำถามนี้ดู "สร้างสรรค์" สำหรับฉัน ฉันจะเริ่มแคมเปญใหม่อีกครั้ง
mickmackusa

คำตอบ:


120

คำตอบจนถึงตอนนี้เป็นจุด ๆ การใช้issetในกรณีนี้จะเร็วกว่าเนื่องจาก

  • ใช้การค้นหาแฮช O (1) บนคีย์ในขณะที่in_arrayต้องตรวจสอบทุกค่าจนกว่าจะพบการจับคู่
  • การเป็น opcode จะมีค่าใช้จ่ายน้อยกว่าการเรียกin_arrayใช้ฟังก์ชันในตัว

สิ่งเหล่านี้สามารถแสดงให้เห็นได้โดยใช้อาร์เรย์ที่มีค่า (10,000 ในการทดสอบด้านล่าง) บังคับin_arrayให้ทำการค้นหาเพิ่มเติม

isset:    0.009623
in_array: 1.738441

สิ่งนี้สร้างขึ้นจากเกณฑ์มาตรฐานของ Jason โดยการกรอกค่าสุ่มและค้นหาค่าที่มีอยู่ในอาร์เรย์เป็นครั้งคราว สุ่มทั้งหมดดังนั้นระวังว่าเวลาจะผันผวน

$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;

ฉันรู้เกี่ยวกับแฮช แต่สงสัยว่าทำไมสิ่งที่คล้ายกันไม่ได้ทำกับค่าอาร์เรย์เมื่อเป็นไปได้ในการเร่งความเร็วฟังก์ชั่นมันจะลดการใช้หน่วยความจำหากใช้ค่าที่คล้ายกันโดยเพียงแค่เพิ่มแฮชพิเศษกับค่า .. ถูกต้อง?
Fabrizio

3
@Fabrizio - ค่าอาร์เรย์สามารถทำซ้ำได้และมีวัตถุที่ไม่สามารถล้างได้ คีย์ต้องไม่ซ้ำกันและต้องเป็นสตริงและจำนวนเต็มได้เท่านั้นซึ่งทำให้สามารถแฮชได้ง่าย ในขณะที่คุณสามารถสร้างแผนที่แบบตัวต่อตัวที่มีทั้งคีย์และค่า แต่นี่ไม่ใช่วิธีการทำงานของอาร์เรย์ของ PHP
David Harkness

3
ในกรณีที่คุณมั่นใจว่าคุณอาร์เรย์มีค่าที่ไม่ซ้ำแล้วมีตัวเลือกอื่น - พลิก + isset
Arkadij Kuzhel

ที่น่าสังเกตว่า isset แบบพลิกยังเร็วกว่าในตัวอย่างนี้มากกว่า in_array: `` $ start = microtime (true); $ foo = array_flip ($ ก); สำหรับ ($ i = 0; $ i <10000; ++ $ i) {isset ($ foo [rand (1, 1000000)]); } $ total_time = microtime (จริง) - $ start; echo "เวลาทั้งหมด (flipped isset):", number_format ($ total_time, 6), PHP_EOL;
Andre Baumeier

@AndreBaumeier ซึ่งเร็วกว่าจะขึ้นอยู่กับขนาดของอาร์เรย์และจำนวนการทดสอบที่คุณจะทำ การพลิกอาร์เรย์องค์ประกอบหนึ่งหมื่นเพื่อทำการทดสอบสามครั้งอาจไม่ได้ผล
David Harkness

43

ซึ่งเร็วกว่า: isset()vsin_array()

isset() เร็วกว่า.

แม้ว่าจะเห็นได้ชัดisset()แต่ควรทดสอบเฉพาะค่าเดียว ในขณะที่in_array()จะวนซ้ำในอาร์เรย์ทั้งหมดทดสอบค่าของแต่ละองค์ประกอบ

microtime()เปรียบเทียบหยาบค่อนข้างง่ายโดยใช้

ผล:

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

หมายเหตุ:ผลลัพธ์เหมือนกันไม่ว่าจะมีอยู่หรือไม่ก็ตาม

รหัส:

<?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;

แหล่งข้อมูลเพิ่มเติม

ฉันขอแนะนำให้คุณดูที่:


ทางออกที่ดี ฉันแปลกใจที่มีคนไม่แบ่งเวลาฟังก์ชั่น / โค้ดมากขึ้นโดยใช้microtime()เครื่องมืออื่น ๆ มีคุณค่าอย่างไม่น่าเชื่อ
nickhar

1
การค้นหาอาร์เรย์ว่างสำหรับคีย์เดียวกันจะเน้นเฉพาะค่าโสหุ้ยของการเรียกใช้in_arrayฟังก์ชันเทียบกับการใช้งานissetในตัว สิ่งนี้จะดีกว่ากับอาร์เรย์ที่มีคีย์สุ่มจำนวนมากและบางครั้งก็ค้นหาคีย์ / ค่าที่มีอยู่
David Harkness

ฉันใช้เกณฑ์มาตรฐานและไมโครไทม์ค่อนข้างน้อย แต่ฉันก็ตระหนักเช่นกันในขณะที่ฉันกำลังทดสอบwhileและforeachในการรีเฟรชแต่ละครั้งฉันได้รับ "ผู้ชนะ" ที่แตกต่างกัน มันขึ้นอยู่กับตัวแปรของเซิร์ฟเวอร์มากเกินไปเสมอและสิ่งที่ดีที่สุดคือการทำซ้ำหลาย ๆ ครั้งในเวลาที่ต่างกันและทำให้ชนะบ่อยขึ้นหรือเพียงแค่รู้ว่าเกิดอะไรขึ้นในเบื้องหลังและรู้ว่าจะเป็นผู้ชนะในที่สุด ไม่ว่าจะเกิดอะไรขึ้น
Fabrizio

@David Harkness คุณเลือกคำตอบของฉันแล้ว ถ้าคุณต้องการมากขึ้นยืนบนไหล่ของฉันและโพสต์คำตอบของคุณเอง :) อย่างไรก็ตามหากค่าโสหุ้ยของฟังก์ชันมีราคาแพงกว่ามากเมื่อเทียบกับisset()อะไรที่ทำให้คุณคิดว่าการส่งอาร์เรย์ที่ใหญ่ขึ้นจะทำให้เร็วขึ้น ?
Jason McCreary

1
@Fabrizio - อ่านข้อมูลเกี่ยวกับฟังก์ชั่นคร่ำเครียดและตารางแฮช
David Harkness

19

การisset()ใช้ประโยชน์จากการค้นหาที่รวดเร็วขึ้นเนื่องจากใช้ตารางแฮชเพื่อหลีกเลี่ยงความจำเป็นในO(n)การค้นหา

ที่สำคัญคือการถกครั้งแรกโดยใช้ฟังก์ชั่น DJB กัญชาO(1)เพื่อตรวจสอบถังของคีย์ถกในทำนองเดียวกันใน จากนั้นระบบจะค้นหาที่เก็บข้อมูลซ้ำ ๆ จนกว่าจะพบคีย์ที่แน่นอนในO(n)ถังที่จะค้นหาแล้วซ้ำจนกว่าคีย์ที่แน่นอนจะพบได้ใน

หลีกเลี่ยงการชนแฮชโดยเจตนาวิธีนี้ให้ประสิทธิภาพที่ดีกว่าin_array()วิธีนี้มีอัตราผลตอบแทนการปฏิบัติงานที่ดีกว่า

โปรดทราบว่าเมื่อใช้isset()ในลักษณะที่คุณแสดงการส่งผ่านค่าสุดท้ายไปยังฟังก์ชันอื่นจำเป็นต้องใช้array_keys()เพื่อสร้างอาร์เรย์ใหม่ การบุกรุกหน่วยความจำสามารถทำได้โดยการจัดเก็บข้อมูลทั้งในคีย์และค่า

อัปเดต

วิธีที่ดีในการดูว่าการตัดสินใจออกแบบโค้ดของคุณส่งผลต่อประสิทธิภาพรันไทม์อย่างไรคุณสามารถตรวจสอบเวอร์ชันที่คอมไพล์แล้วของสคริปต์ของคุณ:

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

ไม่เพียง แต่in_array()ใช้การO(n)ค้นหาที่ไม่มีประสิทธิภาพเท่านั้น แต่ยังต้องเรียกว่าเป็นฟังก์ชัน ( DO_FCALL) ในขณะที่isset()ใช้ opcode ( ZEND_ISSET_ISEMPTY_DIM_OBJ) เดียวสำหรับสิ่งนี้


7

อย่างที่สองจะเร็วขึ้นเนื่องจากกำลังมองหาเฉพาะคีย์อาร์เรย์ที่เฉพาะเจาะจงและไม่จำเป็นต้องวนซ้ำในอาร์เรย์ทั้งหมดจนกว่าจะพบ (จะดูทุกองค์ประกอบอาร์เรย์หากไม่พบ)


แต่ยังขึ้นอยู่กับตำแหน่งของตัวแปรที่ค้นหาในขอบเขตทั่วโลก
el Dude

@ EL2002 คุณช่วยอธิบายรายละเอียดเกี่ยวกับข้อความนั้นได้ไหม
Fabrizio

1
ไมค์จะไม่ดูอาร์เรย์ทั้งหมดแม้ว่าisset()จะไม่พบ?
Fabrizio

1
@Fabrizio ไม่มันไม่จำเป็นต้องทำซ้ำ ภายใน (ใน C) อาร์เรย์ PHP เป็นเพียงตารางแฮช ในการค้นหาค่าดัชนีเดียว C เพียงสร้างแฮชของค่านั้นและค้นหาตำแหน่งที่กำหนดไว้ในหน่วยความจำ มีค่าอยู่ที่นั่นหรือไม่มี
Mike Brant

1
@Fabrizio บทความนี้ให้ภาพรวมที่ดีเกี่ยวกับการแสดงอาร์เรย์ภายในด้วย C โดย PHP nikic.github.com/2012/03/28/…
Mike Brant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.