HashSet กับประสิทธิภาพของรายการ


405

เป็นที่ชัดเจนว่าประสิทธิภาพการค้นหาของHashSet<T>คลาสทั่วไปสูงกว่าList<T>คลาสทั่วไป เพียงเปรียบเทียบคีย์แฮชที่ใช้กับวิธีการเชิงเส้นในList<T>ชั้นเรียน

อย่างไรก็ตามการคำนวณคีย์กัญชาตัวเองอาจใช้เวลารอบการทำงานบางอย่างเพื่อให้สำหรับจำนวนเงินที่เล็ก ๆ HashSet<T>ของรายการที่ค้นหาเชิงเส้นสามารถเป็นทางเลือกที่แท้จริงในการ

คำถามของฉัน: จุดคุ้มทุนอยู่ที่ไหน

เพื่อให้สถานการณ์ง่ายขึ้น (และยุติธรรม) สมมติว่าList<T>คลาสใช้Equals()วิธีการขององค์ประกอบเพื่อระบุรายการ


7
หากคุณต้องการลดเวลาในการค้นหาให้ลองพิจารณาอาร์เรย์และอาร์เรย์ที่เรียงลำดับ เพื่อตอบคำถามนี้อย่างถูกต้องจำเป็นต้องมีการวัดประสิทธิภาพ แต่คุณต้องแจ้งให้เราทราบเพิ่มเติมเกี่ยวกับ T นอกจากนี้ประสิทธิภาพของ HashSet อาจได้รับผลกระทบจากเวลาทำงานของ T.GetHashCode ()
Eldritch Conundrum

คำตอบ:


819

ผู้คนมากมายพูดว่าเมื่อคุณไปถึงขนาดที่ความกังวลเรื่องความเร็วนั้นHashSet<T>จะต้องเอาชนะอยู่ตลอดเวลาList<T>แต่ขึ้นอยู่กับสิ่งที่คุณกำลังทำ

สมมติว่าคุณมีสิ่งList<T>ที่จะมีโดยเฉลี่ย 5 รายการในนั้น List<T>กว่าเป็นจำนวนมากรอบถ้ารายการเดียวคือการเพิ่มหรือลบออกในแต่ละรอบคุณอาจจะดีกว่าการใช้

List<T>ผมทดสอบนี้บนเครื่องของฉันและดีมันจะต้องมีมากมีขนาดเล็กมากที่จะได้รับประโยชน์จาก สำหรับรายการของสตริงสั้น ๆ ข้อดีจะหายไปหลังจากขนาด 5 สำหรับวัตถุหลังขนาด 20

1 item LIST strs time: 617ms
1 item HASHSET strs time: 1332ms

2 item LIST strs time: 781ms
2 item HASHSET strs time: 1354ms

3 item LIST strs time: 950ms
3 item HASHSET strs time: 1405ms

4 item LIST strs time: 1126ms
4 item HASHSET strs time: 1441ms

5 item LIST strs time: 1370ms
5 item HASHSET strs time: 1452ms

6 item LIST strs time: 1481ms
6 item HASHSET strs time: 1418ms

7 item LIST strs time: 1581ms
7 item HASHSET strs time: 1464ms

8 item LIST strs time: 1726ms
8 item HASHSET strs time: 1398ms

9 item LIST strs time: 1901ms
9 item HASHSET strs time: 1433ms

1 item LIST objs time: 614ms
1 item HASHSET objs time: 1993ms

4 item LIST objs time: 837ms
4 item HASHSET objs time: 1914ms

7 item LIST objs time: 1070ms
7 item HASHSET objs time: 1900ms

10 item LIST objs time: 1267ms
10 item HASHSET objs time: 1904ms

13 item LIST objs time: 1494ms
13 item HASHSET objs time: 1893ms

16 item LIST objs time: 1695ms
16 item HASHSET objs time: 1879ms

19 item LIST objs time: 1902ms
19 item HASHSET objs time: 1950ms

22 item LIST objs time: 2136ms
22 item HASHSET objs time: 1893ms

25 item LIST objs time: 2357ms
25 item HASHSET objs time: 1826ms

28 item LIST objs time: 2555ms
28 item HASHSET objs time: 1865ms

31 item LIST objs time: 2755ms
31 item HASHSET objs time: 1963ms

34 item LIST objs time: 3025ms
34 item HASHSET objs time: 1874ms

37 item LIST objs time: 3195ms
37 item HASHSET objs time: 1958ms

40 item LIST objs time: 3401ms
40 item HASHSET objs time: 1855ms

43 item LIST objs time: 3618ms
43 item HASHSET objs time: 1869ms

46 item LIST objs time: 3883ms
46 item HASHSET objs time: 2046ms

49 item LIST objs time: 4218ms
49 item HASHSET objs time: 1873ms

นี่คือข้อมูลที่แสดงเป็นกราฟ:

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือรหัส:

static void Main(string[] args)
{
    int times = 10000000;


    for (int listSize = 1; listSize < 10; listSize++)
    {
        List<string> list = new List<string>();
        HashSet<string> hashset = new HashSet<string>();

        for (int i = 0; i < listSize; i++)
        {
            list.Add("string" + i.ToString());
            hashset.Add("string" + i.ToString());
        }

        Stopwatch timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            list.Remove("string0");
            list.Add("string0");
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item LIST strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");


        timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            hashset.Remove("string0");
            hashset.Add("string0");
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item HASHSET strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
        Console.WriteLine();
    }


    for (int listSize = 1; listSize < 50; listSize+=3)
    {
        List<object> list = new List<object>();
        HashSet<object> hashset = new HashSet<object>();

        for (int i = 0; i < listSize; i++)
        {
            list.Add(new object());
            hashset.Add(new object());
        }

        object objToAddRem = list[0];

        Stopwatch timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            list.Remove(objToAddRem);
            list.Add(objToAddRem);
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item LIST objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");



        timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            hashset.Remove(objToAddRem);
            hashset.Add(objToAddRem);
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item HASHSET objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
        Console.WriteLine();
    }

    Console.ReadLine();
}

8
ขอบคุณมาก! นี่คือคำอธิบายที่ยอดเยี่ยมฉันกำลังมองหาบางอย่างที่สามารถเพิ่มและลบได้เร็วกว่าList<T>สำหรับเอ็นจิ้นเกมและเนื่องจากฉันมักจะมีวัตถุจำนวนมากคอลเลกชันประเภทนี้จะสมบูรณ์แบบ
redcodefinal

17
มีจริงคอลเลกชันในกรอบ NET ที่สลับระหว่างรายการและการดำเนินการ hastable ขึ้นอยู่กับจำนวนของรายการจะมีไปนี้: HybridDictionary
MgSam

8
MS ดูเหมือนจะละทิ้งความคิดเพราะมีเฉพาะรุ่นที่ไม่ใช่ทั่วไปเท่านั้น
MgSam

47
เต็มตามคำตอบนี้มันไม่สามารถตอบคำถามเดิมเกี่ยวกับประสิทธิภาพการค้นหา list vs hashset คุณกำลังทดสอบว่าคุณสามารถแทรกและลบออกได้เร็วแค่ไหนซึ่งต้องใช้เวลามากและมีประสิทธิภาพที่แตกต่างจากการค้นหา ลองอีกครั้งโดยใช้. Contains และกราฟของคุณจะเปลี่ยนไปอย่างมาก
Robert McKee

5
@hypehuman CPU ไม่สามารถทำงานได้โดยตรงกับข้อมูลในหน่วยความจำระบบ แต่ดึงข้อมูลจากหน่วยความจำลงในแคชเพื่อทำงาน มีความล่าช้าอย่างมากระหว่างการร้องขอให้ย้ายหน่วยความจำและหน่วยความจำที่มาถึงจริงดังนั้น CPU มักจะร้องขอหน่วยความจำต่อเนื่องที่มีขนาดใหญ่กว่าให้ย้ายในครั้งเดียว แนวคิดเบื้องหลังนี้คือหน่วยความจำที่ต้องการโดยคำสั่งถัดไปอาจใกล้กับหน่วยความจำที่ใช้โดยคำสั่งก่อนหน้านี้และมักจะอยู่ในแคชแล้ว เมื่อข้อมูลของคุณกระจายไปทั่วหน่วยความจำโอกาสในการได้รับโชคลดลง
Roy T.

70

คุณกำลังดูสิ่งนี้ผิด ใช่การค้นหาเชิงเส้นของรายการจะชนะ HashSet สำหรับรายการจำนวนเล็กน้อย แต่ความแตกต่างด้านประสิทธิภาพมักไม่สำคัญสำหรับคอลเล็กชั่นที่มีขนาดเล็ก โดยทั่วไปคอลเลกชันขนาดใหญ่ที่คุณต้องกังวลเกี่ยวกับและนั่นคือสิ่งที่คุณคิดว่าในแง่ของ Big-O อย่างไรก็ตามหากคุณวัดคอขวดจริงในประสิทธิภาพของ HashSet คุณสามารถลองสร้างรายการแบบไฮบริด / HashSet แต่คุณจะทำเช่นนั้นโดยทำการทดสอบประสิทธิภาพเชิงประจักษ์จำนวนมาก - ไม่ถามคำถามเกี่ยวกับ SO


5
คอลเลกชันขนาดใหญ่ที่คุณต้องกังวลเกี่ยวกับ เราสามารถกำหนดคำถามนั้นใหม่อีกครั้งในรูปแบบwhen small collection becomes large enough to worry about HashSet vs List?นับหมื่นองค์ประกอบหมื่นล้านหรือไม่
om-nom-nom

8
ไม่คุณจะเห็นความแตกต่างด้านประสิทธิภาพอย่างมากเหนือองค์ประกอบสองสามร้อย ประเด็นคือใช้ HashSet ทุกครั้งถ้าคุณทำประเภทการเข้าถึงที่ HashSet ทำได้ดี (เช่นเป็นองค์ประกอบ X ในชุด) หากคอลเลกชันของคุณมีขนาดเล็กจนลิสต์นั้นเร็วกว่าการค้นหาเหล่านั้นจะหายากมาก เป็นคอขวดจริง ๆ ในใบสมัครของคุณ หากคุณสามารถวัดมันให้เป็นหนึ่งได้คุณสามารถลองปรับให้เหมาะสม - แต่ไม่เช่นนั้นคุณจะเสียเวลา
Eloff

15
ถ้าคุณมีคอลเล็กชั่นเล็ก ๆ ที่ถูกตีหลายครั้งในวง? นั่นไม่ใช่สถานการณ์ที่ผิดปกติ
dan-gph

3
@ om-nom-nom - ฉันคิดว่าประเด็นคือมันไม่สำคัญว่าจุดเปลี่ยนจะอยู่ที่ใดเพราะ: "ถ้าประสิทธิภาพเป็นกังวลให้ใช้HashSet<T>ในกรณีจำนวนน้อยซึ่งList<T>อาจเร็วกว่าความแตกต่างก็ไม่มีนัยสำคัญ ."
Scott Smith

66

มันไม่มีสาระสำคัญในการเปรียบเทียบสองโครงสร้างสำหรับประสิทธิภาพที่ทำงานแตกต่างกัน ใช้โครงสร้างที่สื่อถึงเจตนา แม้ว่าคุณจะบอกว่าคุณList<T>จะไม่ซ้ำซ้อนและลำดับการวนซ้ำนั้นไม่สำคัญว่าจะเทียบเคียงได้กับ a HashSet<T>แต่ก็ยังเป็นตัวเลือกที่ใช้งานไม่ได้List<T>เพราะมีความผิดพลาดน้อยกว่า

ที่กล่าวว่าฉันจะตรวจสอบด้านอื่น ๆของการแสดง

+------------+--------+-------------+-----------+----------+----------+-----------+
| Collection | Random | Containment | Insertion | Addition |  Removal | Memory    |
|            | access |             |           |          |          |           |
+------------+--------+-------------+-----------+----------+----------+-----------+
| List<T>    | O(1)   | O(n)        | O(n)      | O(1)*    | O(n)     | Lesser    |
| HashSet<T> | O(n)   | O(1)        | n/a       | O(1)     | O(1)     | Greater** |
+------------+--------+-------------+-----------+----------+----------+-----------+
  • แม้ว่าการเพิ่มเป็น O (1) ในทั้งสองกรณีมันจะค่อนข้างช้าใน HashSet เนื่องจากเกี่ยวข้องกับค่าใช้จ่ายของรหัสแฮชการคำนวณล่วงหน้าก่อนจัดเก็บ

  • ความยืดหยุ่นที่เหนือกว่าของ HashSet มีค่าใช้จ่ายหน่วยความจำ ทุกรายการจะถูกเก็บไว้เป็นวัตถุใหม่พร้อมกับรหัสแฮช บทความนี้อาจให้ความคิดแก่คุณ


11
คำถามของฉัน (หกปีที่ผ่านมา) ไม่ได้เกี่ยวกับประสิทธิภาพทางทฤษฎี
Michael Damatov

1
HashSet อนุญาตการเข้าถึงแบบสุ่มด้วย ElementAt () และฉันคิดว่านั่นเป็นเวลา O (n) นอกจากนี้คุณอาจวางในตารางของคุณว่าแต่ละคอลเลกชันอนุญาตการทำซ้ำ (เช่น: รายการทำ แต่ hashsets ไม่ได้)
Dan W

1
@DanW ในตารางฉันกำลังเปรียบเทียบประสิทธิภาพหมดจดไม่ใช่ลักษณะพฤติกรรม ขอบคุณสำหรับเคล็ดลับ ElementAt
nawfal

1
ElementAt เป็นเพียงส่วนขยาย LINQ .. มันไม่ทำอะไรที่คุณไม่สามารถทำได้และเพิ่มประสิทธิภาพให้ดีขึ้นในอีกวิธีที่คุณเพิ่ม ฉันคิดว่าตารางเหมาะสมกว่าโดยไม่พิจารณา ElementAt เนื่องจากมีวิธีอื่นทั้งหมดที่มีในคลาสเหล่านั้นอย่างชัดเจน
Dinerdo

ขอบคุณสำหรับตารางนี้ในกรณีการใช้งานของฉันฉันต้องเพิ่มและลบเป้าหมายไปยังคอลเลกชันที่มีประชากรทุกครั้งที่เปิดใช้งาน / ปิดการใช้งานและสิ่งนี้ช่วยให้ฉันเลือกได้ถูกต้อง (HashSet)
Casey Hofland

50

ไม่ว่าจะใช้ HashSet <> หรือรายการ <> ลงมาถึงวิธีการที่คุณต้องเข้าสู่คอลเลกชันของคุณ หากคุณต้องการรับประกันลำดับของรายการให้ใช้รายการ หากคุณไม่ใช้ HashSet ให้ Microsoft กังวลเกี่ยวกับการใช้อัลกอริทึมการแฮชและวัตถุ

HashSet จะเข้าถึงรายการโดยไม่ต้องระบุคอลเลกชัน (ความซับซ้อนของO (1)หรือใกล้กับ) และเนื่องจากรายการรับประกันคำสั่งซื้อซึ่งแตกต่างจาก HashSet บางรายการจะต้องระบุ (ความซับซ้อนของ O (n))


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

@ Lu55 - คำถามเกี่ยวกับการค้นหารายการในคอลเล็กชัน สถานการณ์โดยทั่วไปคือการรวบรวมเป็นแบบไดนามิก - รายการอาจถูกเพิ่มหรือลบตั้งแต่ครั้งสุดท้ายที่คุณค้นหารายการที่กำหนด - ดังนั้นดัชนีไม่มีความหมาย (เพราะจะมีการเปลี่ยนแปลง) หากคุณมีการรวบรวมแบบคงที่ (ที่จะไม่เปลี่ยนแปลงในขณะที่คุณทำการคำนวณของคุณ) หรือรายการที่ไม่เคยถูกลบและมีการเพิ่มในตอนท้ายเสมอแล้วListเป็นที่ต้องการเพราะคุณสามารถจำดัชนี - นั่นคือสถานการณ์ที่คุณ กำลังอธิบาย
ToolmakerSteve

คุณสามารถใช้ SortedSet หากคุณต้องการเรียงลำดับ HashSet ยังเร็วกว่ารายการมาก
รักสด

25

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

  1. สตริงขนาดเล็กจำนวนน้อย (12 - 20) ตัว (ความยาวระหว่าง 5 ถึง 10 อักขระ)
  2. สตริงขนาดเล็กจำนวนมาก (~ 10K)
  3. สตริงยาวสองสาม (ความยาวระหว่าง 200 ถึง 1,000 อักขระ)
  4. สตริงยาวมาก (~ 5K)
  5. จำนวนเต็มเล็กน้อย
  6. จำนวนเต็ม (~ 10K) จำนวนเต็ม

และสำหรับแต่ละสถานการณ์ให้ค้นหาค่าที่ปรากฏขึ้น:

  1. ในตอนต้นของรายการ ("เริ่มต้น" ดัชนี 0)
  2. ใกล้กับจุดเริ่มต้นของรายการ ("ต้น", ดัชนี 1)
  3. ที่ตรงกลางของรายการ ("กลาง" นับดัชนี / 2)
  4. ใกล้กับจุดสิ้นสุดของรายการ ("ล่าช้า", ดัชนีนับ -2)
  5. ในตอนท้ายของรายการ ("สิ้นสุด" ดัชนีนับ -1)

ก่อนแต่ละสถานการณ์ฉันสร้างรายการสตริงแบบสุ่มขนาดแล้วป้อนแต่ละรายการเป็นชุดแฮช แต่ละสถานการณ์วิ่ง 10,000 ครั้งโดยพื้นฐานแล้ว:

(ทดสอบรหัสเทียม)

stopwatch.start
for X times
    exists = list.Contains(lookup);
stopwatch.stop

stopwatch.start
for X times
    exists = hashset.Contains(lookup);
stopwatch.stop

ตัวอย่างผลลัพธ์

ทดสอบกับ Windows 7, 12GB Ram, 64 บิต, Xeon 2.8GHz

---------- Testing few small strings ------------
Sample items: (16 total)
vgnwaloqf diwfpxbv tdcdc grfch icsjwk
...

Benchmarks:
1: hashset: late -- 100.00 % -- [Elapsed: 0.0018398 sec]
2: hashset: middle -- 104.19 % -- [Elapsed: 0.0019169 sec]
3: hashset: end -- 108.21 % -- [Elapsed: 0.0019908 sec]
4: list: early -- 144.62 % -- [Elapsed: 0.0026607 sec]
5: hashset: start -- 174.32 % -- [Elapsed: 0.0032071 sec]
6: list: middle -- 187.72 % -- [Elapsed: 0.0034536 sec]
7: list: late -- 192.66 % -- [Elapsed: 0.0035446 sec]
8: list: end -- 215.42 % -- [Elapsed: 0.0039633 sec]
9: hashset: early -- 217.95 % -- [Elapsed: 0.0040098 sec]
10: list: start -- 576.55 % -- [Elapsed: 0.0106073 sec]


---------- Testing many small strings ------------
Sample items: (10346 total)
dmnowa yshtrxorj vthjk okrxegip vwpoltck
...

Benchmarks:
1: hashset: end -- 100.00 % -- [Elapsed: 0.0017443 sec]
2: hashset: late -- 102.91 % -- [Elapsed: 0.0017951 sec]
3: hashset: middle -- 106.23 % -- [Elapsed: 0.0018529 sec]
4: list: early -- 107.49 % -- [Elapsed: 0.0018749 sec]
5: list: start -- 126.23 % -- [Elapsed: 0.0022018 sec]
6: hashset: early -- 134.11 % -- [Elapsed: 0.0023393 sec]
7: hashset: start -- 372.09 % -- [Elapsed: 0.0064903 sec]
8: list: middle -- 48,593.79 % -- [Elapsed: 0.8476214 sec]
9: list: end -- 99,020.73 % -- [Elapsed: 1.7272186 sec]
10: list: late -- 99,089.36 % -- [Elapsed: 1.7284155 sec]


---------- Testing few long strings ------------
Sample items: (19 total)
hidfymjyjtffcjmlcaoivbylakmqgoiowbgxpyhnrreodxyleehkhsofjqenyrrtlphbcnvdrbqdvji...
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0018266 sec]
2: list: start -- 115.76 % -- [Elapsed: 0.0021144 sec]
3: list: middle -- 143.44 % -- [Elapsed: 0.0026201 sec]
4: list: late -- 190.05 % -- [Elapsed: 0.0034715 sec]
5: list: end -- 193.78 % -- [Elapsed: 0.0035395 sec]
6: hashset: early -- 215.00 % -- [Elapsed: 0.0039271 sec]
7: hashset: end -- 248.47 % -- [Elapsed: 0.0045386 sec]
8: hashset: start -- 298.04 % -- [Elapsed: 0.005444 sec]
9: hashset: middle -- 325.63 % -- [Elapsed: 0.005948 sec]
10: hashset: late -- 431.62 % -- [Elapsed: 0.0078839 sec]


---------- Testing many long strings ------------
Sample items: (5000 total)
yrpjccgxjbketcpmnvyqvghhlnjblhgimybdygumtijtrwaromwrajlsjhxoselbucqualmhbmwnvnpnm
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: list: start -- 132.73 % -- [Elapsed: 0.0021517 sec]
3: hashset: start -- 231.26 % -- [Elapsed: 0.003749 sec]
4: hashset: end -- 368.74 % -- [Elapsed: 0.0059776 sec]
5: hashset: middle -- 385.50 % -- [Elapsed: 0.0062493 sec]
6: hashset: late -- 406.23 % -- [Elapsed: 0.0065854 sec]
7: hashset: early -- 421.34 % -- [Elapsed: 0.0068304 sec]
8: list: middle -- 18,619.12 % -- [Elapsed: 0.3018345 sec]
9: list: end -- 40,942.82 % -- [Elapsed: 0.663724 sec]
10: list: late -- 41,188.19 % -- [Elapsed: 0.6677017 sec]


---------- Testing few ints ------------
Sample items: (16 total)
7266092 60668895 159021363 216428460 28007724
...

Benchmarks:
1: hashset: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: hashset: end -- 100.45 % -- [Elapsed: 0.0016284 sec]
3: list: early -- 101.83 % -- [Elapsed: 0.0016507 sec]
4: hashset: late -- 108.95 % -- [Elapsed: 0.0017662 sec]
5: hashset: middle -- 112.29 % -- [Elapsed: 0.0018204 sec]
6: hashset: start -- 120.33 % -- [Elapsed: 0.0019506 sec]
7: list: late -- 134.45 % -- [Elapsed: 0.0021795 sec]
8: list: start -- 136.43 % -- [Elapsed: 0.0022117 sec]
9: list: end -- 169.77 % -- [Elapsed: 0.0027522 sec]
10: list: middle -- 237.94 % -- [Elapsed: 0.0038573 sec]


---------- Testing many ints ------------
Sample items: (10357 total)
370826556 569127161 101235820 792075135 270823009
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0015132 sec]
2: hashset: end -- 101.79 % -- [Elapsed: 0.0015403 sec]
3: hashset: early -- 102.08 % -- [Elapsed: 0.0015446 sec]
4: hashset: middle -- 103.21 % -- [Elapsed: 0.0015618 sec]
5: hashset: late -- 104.26 % -- [Elapsed: 0.0015776 sec]
6: list: start -- 126.78 % -- [Elapsed: 0.0019184 sec]
7: hashset: start -- 130.91 % -- [Elapsed: 0.0019809 sec]
8: list: middle -- 16,497.89 % -- [Elapsed: 0.2496461 sec]
9: list: end -- 32,715.52 % -- [Elapsed: 0.4950512 sec]
10: list: late -- 33,698.87 % -- [Elapsed: 0.5099313 sec]

7
น่าสนใจ ขอบคุณที่ใช้งาน น่าเศร้าที่ฉันสงสัยว่าการสนทนาเหล่านี้ทำให้เกิดการปรับโครงสร้างซ้ำโดยไม่จำเป็น หวังว่าการซื้ออาหารสำหรับคนส่วนใหญ่คือในสถานการณ์กรณีที่เลวร้ายที่สุดของคุณListยังคงใช้เวลาเพียง 0.17 มิลลิวินาทีในการค้นหาครั้งเดียวและไม่น่าจะต้องทดแทนHashSetจนกว่าจะถึงระดับการค้นหาที่ไร้สาระ เมื่อถึงตอนนั้นการใช้รายการมักจะเป็นปัญหาน้อยที่สุด
Paul Walls

นี่ไม่ใช่ข้อมูลจริงในตอนนี้ .. หรืออาจผิดปกติ ... ฉันเพิ่งตรวจสอบค่าเล็ก ๆ จาก 2 ถึง 8 ตัวอักษร รายการ / HashSet ถูกสร้างขึ้นสำหรับแต่ละ 10 ค่า ... HashSet ช้าลงเป็น 30% ... หากใช้ความจุในรายการจะมีผลต่างกันถึง 40% HashSet เร็วขึ้น 10% เฉพาะในกรณีที่เรา List ไม่มีความจุที่ระบุและตรวจสอบแต่ละค่าก่อนที่จะเพิ่มผ่านรายการทั้งหมด
Maxim

หากจำนวนไอเท็มลดลงเหลือ 4 รายการจะชนะอีกครั้งแม้ในสถานการณ์ที่เลวร้ายที่สุด (มีความแตกต่าง 10%) ดังนั้นฉันไม่แนะนำให้ใช้ HashSet สำหรับชุดเล็ก ๆ ของสตริง (สมมติว่า <20) และมันเป็นสิ่งที่แตกต่างจากการทดสอบ "เล็ก ๆ น้อย ๆ " ของคุณ
Maxim

1
@ Maxim ไม่สามารถพูดได้ว่าผลลัพธ์ของฉันคือ "ผิด" - มันเกิดอะไรขึ้นกับเครื่องของฉัน YMMV ในความเป็นจริงฉันเพิ่งวิ่งพวกเขาอีกครั้ง ( gist.github.com/zaus/014ac9b5a78b267aa1643d63d30c7554 ) ในคอมพิวเตอร์สถานะของแข็ง Win10 4.0GHz 16GB และได้ผลลัพธ์ที่คล้ายกัน สิ่งที่ฉันเห็นก็คือประสิทธิภาพของแฮชเซ็ตนั้นมีความสอดคล้องกันมากขึ้นไม่ว่าคีย์การค้นหาจะอยู่ที่ใดหรือมีขนาดใหญ่แค่ไหน แต่เมื่อพอลวอลล์แสดงความคิดเห็นว่าเรากำลังพูดถึง #microoptimization
drzaus

@ Maxim สำหรับการอ้างอิง: dotnetfiddle.net/5taRDd - อย่าลังเลที่จะเล่นกับมัน
drzaus

10

จุดคุ้มทุนจะขึ้นอยู่กับต้นทุนของการคำนวณแฮช การคำนวณ Hash อาจเป็นเรื่องเล็กน้อยหรือไม่ ... :-) ยังมีระบบการเก็บรวบรวมพิเศษระดับภาษาฮิบรูพจนานุกรมเพื่อช่วยให้คุณไม่ต้องกังวลเกี่ยวกับจุดคุ้มทุน


1
คุณต้องคำนึงถึงต้นทุนในการทำการเปรียบเทียบด้วย ในกรณีที่มี (T) HashSet จะทำการเปรียบเทียบเพื่อตรวจสอบว่าไม่มีการชนกันของแฮชกับรายการที่ทำการเปรียบเทียบในทุกรายการที่ดูก่อนที่จะพบว่าถูกต้อง คุณต้องคำนึงถึงการกระจายของ Hash ที่สร้างโดย T.GetHashCode () ราวกับว่าสิ่งนี้จะส่งกลับค่าเดิมที่คุณทำอยู่โดยทั่วไปทำให้ HashSet ทำสิ่งเดียวกับรายการ
มาร์ตินบราวน์

6

คำตอบเช่นเคยคือ " มันขึ้นอยู่กับ " ฉันถือว่าจากแท็กที่คุณพูดถึง C #

ทางออกที่ดีที่สุดของคุณคือการพิจารณา

  1. ชุดของข้อมูล
  2. ข้อกำหนดการใช้งาน

และเขียนกรณีทดสอบ

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

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

เพิ่มเติม:

แน่นอนความคิดเห็นข้างต้นถือว่า 'ประสิทธิภาพ' หมายถึงการเข้าถึงข้อมูล สิ่งอื่นที่ควรพิจารณา: คุณกำลังมองหาอะไรเมื่อคุณพูดว่า "การแสดง"? ประสิทธิภาพของแต่ละค่าค้นหาหรือไม่ เป็นการจัดการชุดค่าขนาดใหญ่ (10,000, 100000 หรือมากกว่า) หรือไม่? มันคือประสิทธิภาพของการกรอกโครงสร้างข้อมูลด้วยข้อมูล? กำลังลบข้อมูลหรือไม่ การเข้าถึงบิตของข้อมูลแต่ละรายการ? แทนที่ค่าหรือไม่ วนซ้ำค่าหรือไม่ การใช้ความจำ? ความเร็วในการคัดลอกข้อมูล? ตัวอย่างเช่นหากคุณเข้าถึงข้อมูลด้วยค่าสตริง แต่ข้อกำหนดด้านประสิทธิภาพหลักของคุณคือการใช้หน่วยความจำน้อยที่สุดคุณอาจมีปัญหาการออกแบบที่ขัดแย้งกัน


5

คุณสามารถใช้ HybridDictionary ซึ่งตรวจจับจุดแตกหักโดยอัตโนมัติและยอมรับค่า Null ซึ่งทำให้จำเป็นเหมือน HashSet


1
โหวตขึ้นจากความคิดนี้ แต่ไม่มีใครได้โปรดใช้สิ่งนี้ในวันนี้ ปฏิเสธไม่รับยาชื่อสามัญ นอกจากนี้พจนานุกรมคือการจับคู่คีย์ - ค่าการตั้งค่าไม่ใช่
nawfal

4

มันขึ้นอยู่กับ. หากคำตอบที่แท้จริงมีความสำคัญจริงๆให้ทำโปรไฟล์และค้นหาคำตอบ หากคุณแน่ใจว่าคุณจะไม่มีองค์ประกอบมากกว่าหนึ่งรายการในชุดให้ไปกับรายการ หากหมายเลขไม่ได้ จำกัด ใช้ HashSet


3

ขึ้นอยู่กับสิ่งที่คุณกำลังคร่ำครวญ หากคีย์ของคุณเป็นจำนวนเต็มคุณอาจไม่จำเป็นต้องมีรายการมากนักก่อนที่ HashSet จะเร็วขึ้น หากคุณป้อนมันลงบนสายอักขระมันจะช้าลงและขึ้นอยู่กับสตริงที่ป้อน

แน่นอนว่าคุณสามารถสร้างมาตรฐานได้อย่างง่ายดาย


3

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


0

ขึ้นอยู่กับปัจจัยหลายอย่าง ... การใช้งานรายการสถาปัตยกรรมซีพียู JVM ความหมายแบบวนซ้ำความซับซ้อนของวิธีการที่เท่าเทียมกันและอื่น ๆ ... ตามเวลาที่รายการมีขนาดใหญ่พอที่จะได้มาตรฐานอย่างมีประสิทธิภาพ (องค์ประกอบ 1000+) ไบนารีที่ใช้ Hash การค้นหาจะเอาชนะการค้นหาเชิงเส้นแบบแฮนด์ดาวน์และความแตกต่างจะเพิ่มขึ้นจากที่นั่นเท่านั้น

หวังว่านี่จะช่วยได้!


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