Hiệu suất Hashset so với danh sách


405

Rõ ràng là hiệu suất tìm kiếm của HashSet<T>lớp chung cao hơn so với List<T>lớp chung . Chỉ cần so sánh khóa dựa trên hàm băm với cách tiếp cận tuyến tính trong List<T>lớp.

Tuy nhiên, việc tính toán khóa băm có thể tự thực hiện một số chu kỳ CPU, vì vậy đối với một lượng nhỏ các mục, tìm kiếm tuyến tính có thể là một sự thay thế thực sự cho HashSet<T>.

Câu hỏi của tôi: hòa vốn ở đâu?

Để đơn giản hóa kịch bản (và công bằng), giả sử rằng List<T>lớp sử dụng Equals()phương thức của phần tử để xác định một mục.


7
Nếu bạn thực sự muốn giảm thiểu thời gian tra cứu, hãy xem xét các mảng và các mảng được sắp xếp. Để trả lời đúng câu hỏi này, cần có một điểm chuẩn, nhưng bạn cần cho chúng tôi biết thêm về T. Ngoài ra, hiệu suất Hashset có thể bị ảnh hưởng bởi thời gian chạy của T.GetHashCode ().
Câu hỏi hóc búa Eldritch

Câu trả lời:


819

Rất nhiều người đang nói rằng một khi bạn đạt đến kích thước mà tốc độ thực sự là một mối quan tâm HashSet<T>sẽ luôn luôn đánh bại List<T>, nhưng điều đó phụ thuộc vào những gì bạn đang làm.

Giả sử bạn có một List<T>thứ sẽ chỉ có trung bình 5 vật phẩm trong đó. Trong một số lượng lớn các chu kỳ, nếu một mục duy nhất được thêm hoặc xóa mỗi chu kỳ, bạn có thể sử dụng tốt hơn một List<T>.

Tôi đã thực hiện một thử nghiệm cho điều này trên máy của mình, và, tốt, nó phải rất rất nhỏ để có được lợi thế từ đó List<T>. Đối với một danh sách các chuỗi ngắn, lợi thế đã biến mất sau kích thước 5, đối với các đối tượng sau kích thước 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

Đây là dữ liệu được hiển thị dưới dạng biểu đồ:

nhập mô tả hình ảnh ở đây

Đây là mã:

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
Cảm ơn bạn rất nhiều! Đây là một lời giải thích tuyệt vời, tôi đang tìm kiếm thứ gì đó có thể thêm và xóa nhanh hơn một List<T>công cụ trò chơi, và vì tôi thường sẽ có một khối lượng lớn các đối tượng, loại bộ sưu tập này sẽ rất hoàn hảo.
redcodefinal

17
Thực sự có một bộ sưu tập trong .NET framework chuyển đổi giữa một danh sách và triển khai có thể chạy được tùy thuộc vào số lượng mục mà nó chứa: HybridDipedia .
MgSam

8
MS dường như đã từ bỏ nó, vì nó chỉ có một phiên bản không chung chung.
MgSam

47
Đầy đủ như câu trả lời này, nó không trả lời được câu hỏi ban đầu liên quan đến danh sách so với hiệu suất tìm kiếm băm. Bạn đang kiểm tra xem bạn có thể chèn và xóa khỏi chúng nhanh như thế nào, việc này tốn nhiều thời gian hơn và các đặc tính hiệu suất khác so với tìm kiếm. Hãy thử lại, sử dụng .Contains và biểu đồ của bạn sẽ thay đổi đáng kể.
Robert McKee

5
@hypehuman CPU không thể hoạt động trực tiếp trên dữ liệu trong bộ nhớ hệ thống mà lấy dữ liệu từ bộ nhớ vào bộ nhớ cache để hoạt động. Có một độ trễ đáng kể giữa yêu cầu bộ nhớ được di chuyển và bộ nhớ thực sự đến, vì vậy CPU sẽ thường yêu cầu một bộ nhớ tiếp giáp lớn hơn được di chuyển cùng một lúc. Ý tưởng đằng sau điều này là bộ nhớ cần thiết cho hướng dẫn tiếp theo có lẽ rất gần với bộ nhớ được sử dụng bởi hướng dẫn trước đó và do đó thường có trong bộ đệm. Khi dữ liệu của bạn nằm rải rác trên bộ nhớ, cơ hội nhận được may mắn sẽ giảm.
Roy T.

70

Bạn đang nhìn sai điều này. Có, một tìm kiếm tuyến tính của Danh sách sẽ đánh bại Hashset cho một số lượng nhỏ vật phẩm. Nhưng sự khác biệt hiệu suất thường không quan trọng đối với các bộ sưu tập nhỏ. Nói chung, đó là những bộ sưu tập lớn mà bạn phải lo lắng, và đó là nơi bạn nghĩ về Big-O . Tuy nhiên, nếu bạn đã đo được một nút cổ chai thực sự đối với hiệu suất Hashset, thì bạn có thể thử tạo Danh sách / Hashset lai, nhưng bạn sẽ làm điều đó bằng cách thực hiện nhiều bài kiểm tra hiệu suất theo kinh nghiệm - không đặt câu hỏi về SO.


5
bộ sưu tập lớn bạn phải lo lắng về . Chúng ta có thể định nghĩa lại câu hỏi đó theo when small collection becomes large enough to worry about HashSet vs List?hàng chục, hàng vạn, hàng tỷ yếu tố?
om-nom-nom

8
Không, bạn sẽ thấy sự khác biệt đáng kể về hiệu suất trên vài trăm yếu tố. Điểm luôn là sử dụng Hashset nếu bạn đang thực hiện các loại truy cập mà Hashset giỏi (ví dụ: phần tử X trong tập hợp.) Nếu bộ sưu tập của bạn quá nhỏ để Danh sách nhanh hơn thì rất hiếm khi những tra cứu đó thực sự là một nút cổ chai trong ứng dụng của bạn. Nếu bạn có thể đo nó là một, tốt thôi bạn có thể cố gắng tối ưu hóa nó - nhưng nếu không thì bạn đang lãng phí thời gian của mình.
Eloff

15
Điều gì nếu bạn có một bộ sưu tập nhỏ được nhấn nhiều lần trong một vòng lặp? Đó không phải là một kịch bản hiếm gặp.
dan-gph

3
@ om-nom-nom - Tôi nghĩ vấn đề là không quan trọng điểm bùng phát là gì, bởi vì: "Nếu hiệu suất là một vấn đề đáng lo ngại, hãy sử dụng HashSet<T>. Trong trường hợp số lượng nhỏ List<T>có thể nhanh hơn, sự khác biệt là không đáng kể . "
Scott Smith

66

Về cơ bản, việc so sánh hai cấu trúc cho hiệu suất hoạt động khác nhau là vô nghĩa . Sử dụng cấu trúc truyền đạt ý định. Ngay cả khi bạn nói rằng bạn List<T>sẽ không có các bản sao và thứ tự lặp không quan trọng làm cho nó có thể so sánh với a HashSet<T>, thì nó vẫn là một lựa chọn kém để sử dụng List<T>vì khả năng chịu lỗi tương đối ít hơn.

Điều đó nói rằng, tôi sẽ kiểm tra một số khía cạnh khác của hiệu suấ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** |
+------------+--------+-------------+-----------+----------+----------+-----------+
  • Mặc dù bổ sung là O (1) trong cả hai trường hợp, nhưng nó sẽ tương đối chậm hơn trong Hashset vì nó liên quan đến chi phí tiền mã hóa mã băm trước khi lưu trữ.

  • Khả năng mở rộng vượt trội của Hashset có chi phí bộ nhớ. Mỗi mục được lưu trữ dưới dạng một đối tượng mới cùng với mã băm của nó. Bài viết này có thể cung cấp cho bạn một ý tưởng.


11
Câu hỏi của tôi (sáu năm trước) không phải là về hiệu suất lý thuyết .
Michael Damatov

1
Hashset cho phép truy cập ngẫu nhiên với ElementAt () và tôi nghĩ đó sẽ là thời gian O (n). Ngoài ra, có lẽ bạn có thể đặt vào bảng của mình xem mỗi bộ sưu tập có cho phép trùng lặp không (ví dụ: danh sách có, nhưng hàm băm không).
Dan W

1
@DanW trong bảng Tôi đang so sánh hiệu suất hoàn toàn, không phải đặc điểm hành vi. Cảm ơn mẹo ElementAt.
nawfal

1
ElementAt chỉ là một phần mở rộng LINQ .. nó không làm gì bạn không thể làm và tối ưu hóa tốt hơn trong một phương pháp khác mà bạn tự thêm vào. Tôi nghĩ rằng bảng có ý nghĩa hơn mà không cần xem xét ElementAt vì tất cả các phương thức khác tồn tại trên các lớp đó một cách rõ ràng.
Dinerdo 23/11/18

1
Cảm ơn bảng này, trong trường hợp sử dụng của tôi, tôi cần thêm và xóa các mục tiêu vào bộ sưu tập dân cư mỗi khi chúng được bật / tắt và điều này giúp tôi đưa ra lựa chọn đúng (Hashset).
Casey Hofland

50

Việc sử dụng Hashset <> hay Danh sách <> tùy thuộc vào cách bạn cần truy cập vào bộ sưu tập của mình . Nếu bạn cần đảm bảo thứ tự của các mặt hàng, sử dụng Danh sách. Nếu bạn không, hãy sử dụng Hashset. Hãy để Microsoft lo lắng về việc thực hiện các thuật toán và đối tượng băm của họ.

Hashset sẽ truy cập các mục mà không cần phải liệt kê bộ sưu tập (độ phức tạp của O (1) hoặc gần nó) và vì một thứ tự Danh sách đảm bảo, không giống như Hashset, một số mục sẽ phải được liệt kê (độ phức tạp của O (n)).


Danh sách có khả năng có thể tính toán bù cho phần tử cụ thể theo chỉ mục của nó (vì tất cả các phần tử là cùng loại và có khả năng chiếm cùng kích thước bộ nhớ). Vì vậy, Danh sách không cần thiết liệt kê các yếu tố của nó
Lu55

@ Lu55 - Câu hỏi là về việc tìm kiếm một mục trong bộ sưu tập. Một kịch bản điển hình là bộ sưu tập là động - các mục có thể đã được thêm hoặc xóa kể từ lần cuối bạn tìm kiếm một mục nhất định - vì vậy một chỉ mục không có ý nghĩa (vì nó sẽ thay đổi). Nếu bạn có một bộ sưu tập tĩnh (sẽ không thay đổi trong khi bạn thực hiện các tính toán của mình) hoặc các mục không bao giờ bị xóa và luôn được thêm vào cuối, thì Listsẽ được ưu tiên, vì bạn có thể nhớ một chỉ mục - đó là tình huống bạn đang mô tả.
ToolmakerSteve

Bạn có thể sử dụng Sắp xếp Sắp xếp nếu bạn cần sắp xếp Hashset. Vẫn nhanh hơn nhiều so với Danh sách.
sống tình yêu

25

Chỉ cần nghĩ rằng tôi sẽ bấm nút với một số điểm chuẩn cho các kịch bản khác nhau để minh họa cho các câu trả lời trước:

  1. Một vài (12 - 20) chuỗi nhỏ (độ dài từ 5 đến 10 ký tự)
  2. Nhiều chuỗi nhỏ (~ 10K)
  3. Một vài chuỗi dài (độ dài từ 200 đến 1000 ký tự)
  4. Nhiều chuỗi dài (~ 5K)
  5. Một vài số nguyên
  6. Nhiều số nguyên (~ 10K)

Và với mỗi kịch bản, tìm kiếm các giá trị xuất hiện:

  1. Vào đầu danh sách ("bắt đầu", chỉ số 0)
  2. Gần đầu danh sách ("sớm", chỉ số 1)
  3. Ở giữa danh sách ("giữa", chỉ số / 2)
  4. Gần cuối danh sách ("trễ", chỉ số đếm-2)
  5. Ở cuối danh sách ("kết thúc", chỉ số đếm-1)

Trước mỗi kịch bản, tôi tạo các danh sách các chuỗi ngẫu nhiên có kích thước ngẫu nhiên và sau đó đưa từng danh sách vào một hàm băm. Mỗi kịch bản chạy 10.000 lần, về cơ bản:

(kiểm tra mã giả)

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

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

Đầu ra mẫu

Đã thử nghiệm trên Windows 7, Ram 12GB, 64 bit, 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
Hấp dẫn. Cảm ơn vì đã chạy nó. Đáng buồn thay, tôi nghi ngờ những cuộc thảo luận này kích hoạt tái cấu trúc không cần thiết. Hy vọng rằng điều đáng mừng đối với hầu hết mọi người là trong trường hợp xấu nhất tuyệt đối của bạn, Listvẫn chỉ mất 0,17 mili giây để thực hiện một lần tra cứu và không có khả năng sẽ yêu cầu thay thế cho HashSetđến khi tần số tra cứu đạt đến mức vô lý. Đến lúc đó, việc sử dụng Danh sách thường là vấn đề ít nhất.
Paul Walls

Đây không phải là thông tin thực tế bây giờ .. Hoặc có thể ban đầu nó sai ... Tôi chỉ kiểm tra các giá trị nhỏ từ 2 đến 8 ký tự. Danh sách / Hashset được tạo cho mỗi 10 giá trị ... Hashset chậm hơn 30% ... Nếu dung lượng trong Danh sách được sử dụng thì chênh lệch thậm chí ~ 40%. Hashset trở nên nhanh hơn chỉ 10% nếu Danh sách của chúng tôi không có dung lượng được chỉ định và kiểm tra từng giá trị trước khi thêm vào toàn bộ danh sách.
Maxim

Nếu số mục giảm xuống còn 4 thì Danh sách lại thắng ngay cả trong trường hợp xấu nhất (với chênh lệch 10%). Vì vậy, tôi không khuyên bạn nên sử dụng Hashset cho bộ sưu tập chuỗi nhỏ (giả sử <20). Và đó là những gì khác với các bài kiểm tra "vài nhỏ" của bạn.
Maxim

1
@Maxim thực sự không thể nói kết quả của tôi là "sai" - đó là những gì đã xảy ra trên máy của tôi. YMMV. Trên thực tế, tôi vừa chạy lại chúng ( gist.github.com/zaus/014ac9b5a78b267aa1643d63d30c7554 ) trên máy tính rắn 16 GB Win10 4.0GHz mới và có kết quả tương tự. Điểm nổi bật mà tôi thấy là hiệu suất băm là nhất quán hơn bất kể khóa tìm kiếm ở đâu hay danh sách lớn như thế nào, trong khi hiệu suất danh sách thay đổi rất nhanh từ tốt hơn đến chậm hơn 300 lần. Nhưng như PaulWalls ban đầu nhận xét chúng ta đang nói chuyện #microoptimization nghiêm túc.
drzaus

@Maxim để tham khảo: dotnetfiddle.net/5taRDd - thoải mái chơi xung quanh nó.
drzaus

10

Mức hòa vốn sẽ phụ thuộc vào chi phí tính toán hàm băm. Các tính toán băm có thể là tầm thường, hoặc không ... :-) Luôn có lớp System.Collections. Specialized.Hy điềuDixi để giúp bạn không phải lo lắng về điểm hòa vốn.


1
Bạn cũng cần phải tính đến chi phí của việc so sánh. Trong trường hợp Chứa (T), Hashset sẽ thực hiện so sánh để kiểm tra xem nó không có xung đột Hash hay không, trong đó Danh sách thực hiện So sánh trên mọi mục mà nó nhìn vào trước khi tìm thấy chính xác. Bạn cũng phải tính đến việc phân phối Băm được tạo bởi T.GetHashCode () vì nếu điều này luôn trả về cùng giá trị mà bạn đang làm cho Hashset thực hiện tương tự như Danh sách.
Martin Brown

6

Câu trả lời, như mọi khi, là " Nó phụ thuộc ". Tôi giả sử từ các thẻ bạn đang nói về C #.

Đặt cược tốt nhất của bạn là xác định

  1. Tập dữ liệu
  2. Yêu cầu sử dụng

và viết một số trường hợp thử nghiệm.

Nó cũng phụ thuộc vào cách bạn sắp xếp danh sách (nếu nó được sắp xếp hoàn toàn), loại so sánh nào cần được thực hiện, thao tác "So sánh" mất bao lâu cho đối tượng cụ thể trong danh sách hoặc thậm chí cách bạn dự định sử dụng bộ sưu tập.

Nói chung, cách tốt nhất để chọn không quá nhiều dựa trên kích thước dữ liệu bạn đang làm việc, mà là cách bạn dự định truy cập nó. Bạn có từng phần dữ liệu được liên kết với một chuỗi cụ thể hoặc dữ liệu khác không? Một bộ sưu tập dựa trên hàm băm có lẽ sẽ là tốt nhất. Thứ tự của dữ liệu bạn đang lưu trữ có quan trọng không, hoặc bạn sẽ cần truy cập tất cả dữ liệu cùng một lúc? Một danh sách thường xuyên có thể tốt hơn sau đó.

Bổ sung:

Tất nhiên, các ý kiến ​​trên của tôi cho rằng 'hiệu suất' có nghĩa là truy cập dữ liệu. Một cái gì đó khác để xem xét: bạn đang tìm kiếm gì khi bạn nói "hiệu suất"? Là hiệu suất cá nhân tìm kiếm? Có phải nó quản lý các bộ giá trị lớn (10000, 100000 trở lên) không? Đây có phải là hiệu suất của việc lấp đầy cấu trúc dữ liệu với dữ liệu? Xóa dữ liệu? Truy cập từng bit dữ liệu? Thay thế giá trị? Lặp lại các giá trị? Sử dụng bộ nhớ? Tốc độ sao chép dữ liệu? Ví dụ: Nếu bạn truy cập dữ liệu theo một giá trị chuỗi, nhưng yêu cầu hiệu suất chính của bạn là sử dụng bộ nhớ tối thiểu, bạn có thể gặp các vấn đề thiết kế mâu thuẫn.


5

Bạn có thể sử dụng một HybridDipedia tự động phát hiện điểm phá vỡ và chấp nhận các giá trị null, làm cho nó trở nên thiết yếu giống như Hashset.


1
Nâng cao ý tưởng này, nhưng không ai vui lòng sử dụng nó ngay hôm nay. Nói không với không chung chung. Ngoài ra một từ điển là ánh xạ khóa-giá trị, không được đặt.
nawfal

4

Nó phụ thuộc. Nếu câu trả lời chính xác thực sự quan trọng, hãy làm một số hồ sơ và tìm hiểu. Nếu bạn chắc chắn rằng bạn sẽ không bao giờ có nhiều hơn một số yếu tố nhất định trong tập hợp, hãy đi với Danh sách. Nếu số lượng không bị ràng buộc, hãy sử dụng Hashset.


3

Phụ thuộc vào những gì bạn đang băm. Nếu khóa của bạn là số nguyên, có lẽ bạn không cần nhiều vật phẩm trước khi Hashset nhanh hơn. Nếu bạn đang khóa nó trên một chuỗi thì nó sẽ chậm hơn và phụ thuộc vào chuỗi đầu vào.

Chắc chắn bạn có thể đánh dấu một điểm chuẩn khá dễ dàng?


3

Một yếu tố bạn không tính đến là sự mạnh mẽ của hàm GetHashcode (). Với chức năng băm hoàn hảo, Hashset rõ ràng sẽ có hiệu suất tìm kiếm tốt hơn. Nhưng khi hàm băm giảm dần thì thời gian tìm kiếm Hashset cũng giảm theo.


0

Phụ thuộc vào rất nhiều yếu tố ... Việc thực hiện danh sách, kiến ​​trúc CPU, JVM, ngữ nghĩa vòng lặp, độ phức tạp của phương thức bằng, v.v ... Vào thời điểm danh sách đủ lớn để đánh giá hiệu quả (1000+ phần tử), nhị phân dựa trên Hash tra cứu đánh bại các tìm kiếm tuyến tính thực tế và sự khác biệt chỉ tăng lên từ đó.

Hi vọng điêu nay co ich!


1
JVM ... hoặc CLR :-)
bvgheluwe
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.