เหตุใดจึงเร็วกว่าเพื่อตรวจสอบว่าพจนานุกรมมีรหัสหรือไม่แทนที่จะตรวจพบข้อยกเว้นในกรณีที่ไม่มี


234

ลองนึกภาพรหัส:

public class obj
{
    // elided
}

public static Dictionary<string, obj> dict = new Dictionary<string, obj>();

วิธีที่ 1

public static obj FromDict1(string name)
{
    if (dict.ContainsKey(name))
    {
        return dict[name];
    }
    return null;
}

วิธีที่ 2

public static obj FromDict2(string name)
{
    try
    {
        return dict[name];
    }
    catch (KeyNotFoundException)
    {
        return null;
    }
}

ฉันอยากรู้ว่าประสิทธิภาพการทำงานของทั้งสองฟังก์ชั่นแตกต่างกันหรือไม่เพราะฟังก์ชั่นแรกควรช้ากว่าฟังก์ชั่นที่สองเนื่องจากต้องตรวจสอบสองครั้งหากพจนานุกรมมีค่าในขณะที่ฟังก์ชั่นที่สองจำเป็นต้องเข้าถึงพจนานุกรมเท่านั้น ครั้งเดียว แต่ว้าวมันตรงกันข้ามจริงๆ:

วนซ้ำสำหรับค่า 1,000,000 (มี 100,000 ที่มีอยู่และ 900,000 ที่ไม่มีอยู่):

ฟังก์ชั่นแรก: 306 มิลลิวินาที

ฟังก์ชั่นที่สอง: 20483 มิลลิวินาที

ทำไมถึงเป็นอย่างนั้น?

แก้ไข: ในขณะที่คุณสามารถสังเกตเห็นในความคิดเห็นด้านล่างคำถามนี้ประสิทธิภาพของฟังก์ชั่นที่สองจริง ๆ แล้วดีกว่าหนึ่งในกรณีที่มี 0 คีย์ที่ไม่มีอยู่ แต่เมื่อไม่มีคีย์อย่างน้อย 1 คีย์ขึ้นไปประสิทธิภาพที่สองจะลดลงอย่างรวดเร็ว


39
ทำไมคนแรกควรช้าลง? ที่จริงแล้วในแวบแรกฉันจะบอกว่ามันควรจะเร็วกว่าContainsKeyนี้O(1)... คาดว่า
Patryk Ćwiek


8
@Petr มีคำแนะนำอื่น ๆ อีกมากมายที่เกี่ยวข้องในการยกเว้นการขว้างปากว่ามีO(1)การค้นหาในพจนานุกรม ... โดยเฉพาะอย่างยิ่งนับตั้งแต่การทำสองO(1)การดำเนินงานยังคง O(1)asymptotically
Patryk Ćwiek

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

6
อย่าลืมว่าคุณได้เปรียบเทียบค่าใช้จ่ายในการตรวจสอบค่าที่หายไปกว่าหนึ่งล้านค่าเทียบกับการยกเว้นข้อยกเว้นเป็นล้าน แต่ทั้งสองวิธียังแตกต่างกันในค่าใช้จ่ายในการเข้าถึงค่าที่มีอยู่ หากคีย์ที่หายไปนั้นหายากพอวิธียกเว้นจะเร็วกว่าทั้งหมดแม้ว่าจะมีค่าใช้จ่ายสูงกว่าเมื่อคีย์ขาดหายไป
alexis

คำตอบ:


404

ในอีกด้านหนึ่งการยกเว้นการขว้างปามีราคาแพงโดยเนื้อแท้เพราะกองจะต้องคลายและอื่น ๆ
ในทางกลับกันการเข้าถึงค่าในพจนานุกรมโดยใช้กุญแจนั้นมีราคาถูกเพราะเป็นการดำเนินการที่รวดเร็ว O (1)

BTW: วิธีที่ถูกต้องในการทำเช่นนี้คือการใช้ TryGetValue

obj item;
if(!dict.TryGetValue(name, out item))
    return null;
return item;

การเข้าถึงพจนานุกรมเพียงครั้งเดียวแทนที่จะเป็นสองครั้ง
หากคุณต้องการคืนค่าnullหากไม่มีคีย์รหัสข้างต้นสามารถทำให้เข้าใจง่ายขึ้นอีก:

obj item;
dict.TryGetValue(name, out item);
return item;

ใช้งานได้เพราะTryGetValueตั้งค่าitemเป็นnullหากไม่มีคีย์พร้อมnameอยู่


4
ฉันอัปเดตการทดสอบตามคำตอบและด้วยเหตุผลบางอย่างแม้ว่าฟังก์ชั่นที่แนะนำจะเร็วกว่า แต่ที่จริงแล้วมันไม่สำคัญมาก: ต้นฉบับ 264 ms, 258ms แนะนำหนึ่ง
Petr

52
@Petr: ใช่มันไม่สำคัญเพราะการเข้าถึงพจนานุกรมนั้นเร็วมากมันไม่สำคัญเลยถ้าคุณทำมันครั้งเดียวหรือสองครั้ง ส่วนใหญ่ของ 250 ms เหล่านั้นมักใช้ในลูปทดสอบเอง
Daniel Hilgarth

4
สิ่งนี้เป็นเรื่องดีที่จะรู้เพราะบางครั้งคนเรารู้สึกว่าการทิ้งข้อยกเว้นเป็นวิธีที่ดีกว่าหรือสะอาดกว่าในการจัดการกับสถานการณ์เช่นไฟล์ที่ไม่มีตัวชี้หรือตัวชี้โมฆะไม่ว่าสถานการณ์นั้นเป็นเรื่องปกติหรือไม่
LarsH

4
@ LarsH มันขึ้นอยู่กับสิ่งที่คุณทำ ในขณะที่ไมโครบล็อกที่เรียบง่ายเช่นนี้จะแสดงบทลงโทษที่รุนแรงมากสำหรับข้อยกเว้นเมื่อลูปของคุณเริ่มต้นรวมถึงกิจกรรมไฟล์หรือฐานข้อมูล เปรียบเทียบตารางที่ 1 และ 2: codeproject.com/Articles/11265/…
Dan Is Fiddling โดย Firelight

8
@LarsH โปรดทราบว่าเมื่อพยายามเข้าถึงไฟล์ (หรือทรัพยากรภายนอกอื่น ๆ ) อาจเปลี่ยนสถานะระหว่างการตรวจสอบและการพยายามเข้าถึงที่เกิดขึ้นจริง ในกรณีเหล่านี้การใช้ข้อยกเว้นเป็นวิธีที่ถูกต้อง ดูคำตอบของ Stephen Cสำหรับคำถามนี้สำหรับข้อมูลเชิงลึกเพิ่มเติม
yoniLavi

6

พจนานุกรมได้รับการออกแบบมาโดยเฉพาะเพื่อทำการค้นหาคีย์แบบเร็วสุด พวกเขาจะถูกนำมาใช้เป็นแฮชเทเบิลและรายการเพิ่มเติมได้เร็วขึ้นพวกเขาจะเกี่ยวข้องกับวิธีการอื่น ๆ การใช้เอ็นจิ้นข้อยกเว้นนั้นควรจะทำก็ต่อเมื่อวิธีการของคุณล้มเหลวในการทำสิ่งที่คุณออกแบบมาให้ทำเพราะมันเป็นชุดของวัตถุขนาดใหญ่ที่ให้คุณมีฟังก์ชั่นมากมายสำหรับจัดการข้อผิดพลาด ฉันสร้างคลาสไลบรารีทั้งหมดหนึ่งครั้งพร้อมทุกสิ่งที่ล้อมรอบด้วยลอง catch catch ครั้งเดียวและก็ตกใจเมื่อเห็นผลลัพธ์ debug ซึ่งมีบรรทัดแยกกันสำหรับทุกข้อยกเว้นมากกว่า 600 ข้อ!


1
เมื่อผู้พัฒนาภาษาตัดสินใจที่จะใช้ความพยายามในการปรับให้เหมาะสมตารางแฮชจะได้รับการจัดลำดับความสำคัญเนื่องจากมีการใช้งานบ่อยครั้งในวงด้านในที่อาจเป็นปัญหาคอขวด คาดว่าจะมีการใช้ข้อยกเว้นน้อยกว่ามากในกรณีที่ผิดปกติ ("ยอดเยี่ยม" เพื่อพูด) ดังนั้นจึงมักไม่ถือว่ามีความสำคัญต่อประสิทธิภาพ
Barmar

"พวกมันถูกนำไปใช้เป็นแฮชเทเบิลและยิ่งรายการเร็วขึ้นพวกมันก็จะสัมพันธ์กับวิธีอื่น" แน่นอนว่าไม่เป็นความจริงหากถังบรรจุเต็ม!?!
AnthonyLambert

1
@AnthonyLambert สิ่งที่เขาพยายามจะพูดคือการค้นหา hashtable มีความซับซ้อนของเวลา O (1) ในขณะที่การค้นหาต้นไม้ค้นหาแบบไบนารีจะมี O (log (n)); ต้นไม้ช้าลงตามจำนวนขององค์ประกอบที่เพิ่มขึ้น asymptotically ในขณะที่ hashtable ไม่ ดังนั้นความได้เปรียบความเร็วของ hashtable จะเพิ่มขึ้นตามจำนวนขององค์ประกอบแม้ว่ามันจะช้าลงก็ตาม
Doval

@AnthonyLambert ภายใต้การใช้งานปกติมีการชนกันน้อยมากใน hashtable ของพจนานุกรม หากคุณกำลังใช้ hashtable และถังของคุณเติมคุณมี waaaaay มากเกินไปรายการ (หรือถังน้อยเกินไป) ในกรณีนี้ถึงเวลาที่จะใช้ hashtable ที่กำหนดเอง
AndrewS
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.