Tại sao nó nhanh hơn để kiểm tra nếu từ điển có chứa khóa, thay vì bắt ngoại lệ trong trường hợp không?


234

Hãy tưởng tượng mã:

public class obj
{
    // elided
}

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

Phương pháp 1

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

Phương pháp 2

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

Tôi tò mò liệu có sự khác biệt về hiệu suất của 2 chức năng này hay không, bởi vì chức năng thứ nhất NÊN TRỞ LÊN so với chức năng thứ hai - với điều kiện là nó cần kiểm tra hai lần nếu từ điển có chứa giá trị, trong khi chức năng thứ hai chỉ cần truy cập từ điển một lần nhưng WOW, nó thực sự ngược lại:

Lặp lại cho 1 000 000 giá trị (với 100 000 hiện có và 900 000 không tồn tại):

chức năng đầu tiên: 306 mili giây

chức năng thứ hai: 20483 mili giây

Tại sao vậy?

EDIT: Như bạn có thể nhận thấy trong các bình luận bên dưới câu hỏi này, hiệu suất của chức năng thứ hai thực sự tốt hơn một chút so với lần đầu tiên trong trường hợp có 0 khóa không tồn tại. Nhưng một khi có ít nhất 1 hoặc nhiều khóa không tồn tại, hiệu suất của khóa thứ hai sẽ giảm nhanh chóng.


39
Tại sao cái đầu tiên nên chậm hơn? Trên thực tế, ngay từ cái nhìn đầu tiên, tôi muốn nói rằng nó sẽ nhanh hơn, ContainsKeyđược mong đợi O(1)...
Patryk wiek


8
@Petr Có rất nhiều hướng dẫn liên quan đến việc ném ngoại lệ hơn là O(1)tra cứu trong từ điển ... Đặc biệt là khi thực hiện hai O(1)thao tác vẫn không có triệu chứng O(1).
Patryk wiek

9
Như đã được lưu ý trong câu trả lời tốt dưới đây, ném ngoại lệ là tốn kém. Tên của họ cho thấy điều này: chúng được dành riêng cho các trường hợp ngoại lệ . Nếu bạn đang chạy một vòng lặp trong đó bạn truy vấn từ điển một triệu lần cho các khóa không tồn tại, thì đó sẽ là một trường hợp đặc biệt. Nếu bạn đang truy vấn từ điển cho các khóa và đó là trường hợp tương đối phổ biến mà khóa đó sẽ không có mặt, thì nên kiểm tra trước.
Jason R

6
Đừng quên rằng bạn chỉ so sánh chi phí kiểm tra một triệu giá trị vắng mặt, so với việc ném một triệu ngoại lệ. Nhưng hai phương pháp cũng khác nhau về chi phí truy cập một giá trị hiện có . Nếu thiếu khóa là đủ hiếm, phương pháp ngoại lệ sẽ nhanh hơn tất cả, mặc dù chi phí cao hơn khi không có khóa.
alexis

Câu trả lời:


404

Một mặt, việc ném ngoại lệ vốn đã rất tốn kém , bởi vì ngăn xếp phải không được giải quyết, v.v.
Mặt khác, việc truy cập một giá trị trong từ điển bằng khóa của nó là rẻ, bởi vì đó là thao tác O (1) nhanh chóng.

BTW: Cách chính xác để làm điều này là sử dụng TryGetValue

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

Điều này chỉ truy cập từ điển một lần thay vì hai lần.
Nếu bạn thực sự muốn trả về nullnếu khóa không tồn tại, đoạn mã trên có thể được đơn giản hóa hơn nữa:

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

Công trình này, vì TryGetValuebộ itemđể nullnếu không có chìa khóa với nametồn tại.


4
Tôi đã cập nhật bài kiểm tra của mình theo câu trả lời và vì một số lý do, mặc dù chức năng được đề xuất là IS nhanh hơn, nhưng thực tế nó không đáng kể lắm: 264 ms gốc, 258ms đề xuất một
Petr

52
@Petr: Vâng, nó không đáng kể, vì truy cập từ điển rất nhanh, nó không thực sự quan trọng nếu bạn làm điều đó một hoặc hai lần. Hầu hết trong số 250 ms rất có thể được dành cho chính vòng lặp thử nghiệm.
Daniel Hilgarth

4
Điều này là tốt để biết, bởi vì đôi khi người ta có ấn tượng rằng ném ngoại lệ là cách tốt hơn hoặc sạch hơn để xử lý một tình huống như tệp không tồn tại hoặc con trỏ null, bất kể các tình huống đó là phổ biến và không xem xét chi phí hiệu suất.
LarsH

4
@LarsH nó cũng phụ thuộc vào những gì bạn đang làm. Mặc dù các dấu hiệu vi mô đơn giản như thế này cho thấy các hình phạt thực sự lớn đối với các trường hợp ngoại lệ một khi các vòng lặp của bạn bắt đầu bao gồm các hoạt động tệp hoặc cơ sở dữ liệu ném một ngoại lệ vào mỗi lần lặp rất ít ảnh hưởng đến hiệu suất. So sánh bảng thứ 1 và thứ 2: codeproject.com/Articles/11265/ từ
Dan đang đấu tranh với Firelight

8
@LarsH Cũng lưu ý rằng khi cố gắng truy cập một tệp (hoặc một số tài nguyên bên ngoài khác), nó có thể thay đổi trạng thái giữa kiểm tra và nỗ lực truy cập thực tế. Trong những trường hợp này, sử dụng ngoại lệ là cách chính xác để đi. Xem câu trả lời của Stephen C cho câu hỏi này để có cái nhìn sâu sắc hơn.
yoniLavi

6

Từ điển được thiết kế đặc biệt để thực hiện tra cứu khóa siêu nhanh. Chúng được triển khai dưới dạng hashtables và càng nhiều mục thì chúng càng nhanh hơn so với các phương thức khác. Việc sử dụng công cụ ngoại lệ chỉ được thực hiện khi phương thức của bạn không thực hiện được những gì bạn đã thiết kế vì nó là một tập hợp lớn các đối tượng cung cấp cho bạn rất nhiều chức năng để xử lý lỗi. Tôi đã xây dựng toàn bộ một lớp thư viện một lần với mọi thứ được bao quanh bởi các khối bắt thử một lần và rất kinh ngạc khi thấy đầu ra gỡ lỗi chứa một dòng riêng biệt cho mỗi một trong số hơn 600 trường hợp ngoại lệ!


1
Khi những người triển khai ngôn ngữ đang quyết định nơi dành những nỗ lực tối ưu hóa, các bảng băm sẽ được ưu tiên vì chúng được sử dụng thường xuyên, thường trong các vòng lặp bên trong có thể bị tắc nghẽn. Các trường hợp ngoại lệ được dự kiến ​​chỉ được sử dụng ít thường xuyên hơn, trong các trường hợp bất thường ("ngoại lệ", có thể nói), vì vậy chúng thường không được coi là quan trọng đối với hiệu suất.
Barmar

"Chúng được triển khai dưới dạng hashtables và càng nhiều mục thì chúng càng nhanh hơn so với các phương thức khác." chắc chắn điều đó không đúng nếu xô đầy?!?!
AnthonyLambert

1
@AnthonyLambert Điều anh ấy muốn nói là việc tìm kiếm một hashtable có độ phức tạp thời gian O (1), trong khi tìm kiếm cây nhị phân sẽ có O (log (n)); cây chậm lại khi số lượng phần tử tăng không theo triệu chứng, trong khi hashtable thì không. Do đó, lợi thế tốc độ của hashtable tăng theo số lượng phần tử, mặc dù nó làm rất chậm.
Doval

@AnthonyLambert Trong sử dụng bình thường, có rất ít va chạm trong hashtable của Dictionary. Nếu bạn đang sử dụng hashtable và xô của bạn đầy, bạn có quá nhiều mục (hoặc quá ít nhóm). Trong trường hợp đó, đã đến lúc sử dụng một hashtable tùy chỉnh.
AndrewS
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.