2013-04-19 12 views
223

Wyobraźmy sobie kod:Dlaczego szybciej jest sprawdzić, czy słownik zawiera klucz, zamiast wychwycić wyjątek na wypadek, gdyby nie był?

public class obj 
{ 
    // elided 
} 

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

Metoda 1

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

Method 2

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

Byłem ciekaw, czy istnieje różnica w wydajności tych 2 funkcje, ponieważ pierwszy powinien być WOLNY niż drugi - zważywszy, że musi sprawdź dwa razy, czy słownik zawiera wartość, podczas gdy druga funkcja musi uzyskać dostęp do słownika tylko jeden raz, ale WOW, to jest faktycznie odwrotnie:

Pętla dla 1 000 000 wartości (przy 100 000 istniejących i 900 000 nieistniejących):

pierwsza funkcja: 306 milisekund

Druga funkcja: 20483 milisekund

Dlaczego tak jest?

EDYCJA: Jak można zauważyć w komentarzach poniżej tego pytania, wydajność drugiej funkcji jest w rzeczywistości nieco lepsza niż pierwsza, w przypadku gdy istnieje 0 kluczy nie istniejących. Ale gdy istnieje co najmniej jeden nieistniejący klucz, wydajność drugiego szybko maleje.

+36

Dlaczego pierwszy * powinien * być wolniejszy? Właściwie, na pierwszy rzut oka powiedziałbym, że powinien być szybszy, 'ContainsKey' jest oczekiwany' O (1) '... –

+2

http://msdn.microsoft.com/en-us/library/vstudio/ms229009 (v = vs.100) .aspx – Habib

+0

Zobacz http: // stackoverflow.com/a/52390/759019 –

Odpowiedz

378

Z jednej strony, throwing exceptions is inherently expensive, ponieważ stos musi być rozwijana itp
Z drugiej strony, dostęp do wartości w słowniku przez jego klucz jest tania, ponieważ jest to szybka, O (1) operacji.

BTW: Poprawny sposób to zrobić jest użycie TryGetValue

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

ten uzyskuje dostęp do słownika tylko raz, a nie dwa razy.
Jeśli naprawdę chcą tylko wrócić null jeśli klucz nie istnieje, powyższy kod można uprościć dalej:

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

To działa, ponieważ TryGetValue zestawy item do null jeśli żaden klawisz z name istnieje.

+4

Zaktualizowałem mój test zgodnie z odpowiedzią i z jakiegoś powodu, mimo że sugerowana funkcja JEST szybsza, w rzeczywistości nie jest zbyt znacząca: 264 ms oryginału, 258 ms sugeruje jeden – Petr

+51

@Petr: Tak, to nie jest znaczące, ponieważ dostęp do słownika jest bardzo szybko, nie ma znaczenia, czy robisz to raz czy dwa razy. Większość z tych 250 ms najprawdopodobniej jest zużywana w samej pętli testowej. –

+4

Dobrze o tym wiedzieć, ponieważ czasami można odnieść wrażenie, że wyrzucanie wyjątków jest lepszym lub czystszym sposobem radzenia sobie z sytuacją, taką jak nieistniejący plik lub wskaźnik zerowy, niezależnie od tego, czy te sytuacje są powszechne, i bez uwzględnienia kosztów wydajności. – LarsH

6

Słowniki zostały specjalnie zaprojektowane do wykonywania superszybkich wyszukiwań kluczowych. Są one implementowane jako hashtables, a im więcej wpisów, tym szybciej są one względem innych metod. Używanie mechanizmu wyjątku powinno być wykonywane tylko wtedy, gdy twoja metoda nie wykonała tego, co zaprojektowałeś, ponieważ jest to duży zestaw obiektów, który zapewnia wiele funkcji do obsługi błędów. Raz zbudowałem całą klasę biblioteki, a wszystko, co zostało otoczone przez wypróbowanie bloków catch, było przerażone, gdy zobaczyłem wynik debugowania, który zawierał osobną linię dla każdego z ponad 600 wyjątków!

+1

Kiedy decydenci językowi decydują, gdzie wydać wysiłek na optymalizację, tabele mieszania zostaną potraktowane priorytetowo, ponieważ są często używane, często w wewnętrznych pętlach, które mogą być wąskimi gardłami. Oczekuje się, że wyjątki będą stosowane znacznie rzadziej, w nietypowych ("wyjątkowych", że tak powiem) przypadkach, więc zwykle nie są uważane za ważne dla wydajności. – Barmar

+0

"Są one implementowane jako hashtables, a im więcej wpisów, tym szybciej są one względem innych metod." z pewnością nie jest to prawdą, jeśli wiadra się wypełnią?!?! – AnthonyLambert

+0

@AnthonyLambert Próbuje powiedzieć, że wyszukiwanie hashtable ma złożoność czasu O (1), podczas gdy wyszukiwanie drzewa binarnego ma O (log (n)); drzewo zwalnia, gdy liczba elementów wzrasta asymptotycznie, a nie ma takiego hashtable. W związku z tym przewaga prędkości z hashtable wzrasta wraz z liczbą elementów, choć robi to powoli. – Doval

Powiązane problemy