2012-09-12 14 views
8

Co się stanie, jeśli switch ma więcej niż 5000 case. Jakie są wady i jak możemy je zastąpić czymś szybszym?Przełącznik z ogromną liczbą przypadków

Uwaga: Nie spodziewam się używać tablicy do przechowywania skrzynek, ponieważ jest taka sama.

+1

Dlaczego chcesz porównać 5000 przypadków? Nie możesz skompresować/zreorganizować kodu, aby uzyskać mniej przypadków w każdym segmencie kodu? – mmoment

+3

Możesz użyć ** tablicy wskaźników funkcji **, jest to dużo szybszy czas niż jakiekolwiek porównanie, ALE moje pytanie jest ... jak możesz ** zarządzać i utrzymywać ** kod za pomocą instrukcji 'switch/case' z 5000 przypadków ? –

+6

Jeśli chcesz przełączać się między 5000 przypadków, brzmi to jak zły zamiar projektu. W przeciwnym razie pójdę też po wskaźniki funkcji. – dtech

Odpowiedz

11

Nie ma konkretnego powodu, aby sądzić, że chcesz czegoś innego niż instrukcji switch/case (i rzeczywiście bym się aktywnie spodziewał, że nie będzie to pomocne). Kompilator powinien utworzyć wydajny kod dyspozytorski, który może obejmować kombinację statycznych tabel [sparse] i bezpośredniego indeksowania, rozgałęzień binarnych itd .; ma wgląd w wartości statyczne skrzynek i powinien wykonać doskonałą pracę (retuning to w locie za każdym razem, gdy zmieniasz przypadki, podczas gdy nowe wartości, które nie pasują do ręcznie wykonanego podejścia - takie jak znacznie różniące się wartości gdy masz dość spakowane wyszukiwanie tablicy - może to wymagać przerobienia kodu lub po cichu spowodować rozdęcie pamięci lub spadek wydajności).

Ludzie naprawdę troszczyli się o tego rodzaju rzeczy, kiedy C próbował wygrać z programistami montażu hard-core ... kompilatory były odpowiedzialne za generowanie dobrego kodu. Innymi słowy - jeśli nie jest (wymiernie) zepsute, nie naprawiaj tego.

Ogólnie rzecz biorąc, świetnie jest być ciekawym tego typu rzeczy i uzyskać pomysły ludzi na temat alternatywnych rozwiązań i ich wpływu na wydajność, ale jeśli naprawdę jesteś pewien, że Twoja opieka i różnica w wydajności mogą być przydatne dla twojego programu (zwłaszcza jeśli profilowanie sugeruje to), a następnie zawsze testuj swój program wykonując prawdziwą pracę.

+0

Tak, uważam, że kompilator sprawi, że przełącznik będzie skuteczny, chociaż 5000 przypadków nie jest normalne. Prawdopodobnie tablica wskaźników funkcyjnych jest lepsza, przynajmniej kod będzie wyglądał lepiej. – Mine

+0

@Mine: tablica wskaźnika funkcji sprawi, że kod będzie czytelny. ale szukam jakiegoś szybszego rozwiązania. – MarsRover

+0

Tablica wskaźników funkcji @MarsRover jest szybka. Jedno mnożenie, szacunek wskaźnik i połączenie. Trudno mieć mniej instrukcji. Co więcej, zgadzam się z Tonym, jeśli projektowanie nie jest problemem, to czy jesteś pewien, że wydajność się obniża? –

3

Spróbuj użyć klasy Dictionary z wartościami Delegata. Przynajmniej sprawia, że ​​kod jest trochę bardziej czytelny.

+3

Jedynym wyraźnym wymaganiem pytania było "coś szybciej" ... mapa hash będzie prawdopodobnie znacznie wolniejsza, podobnie jak wymuszone wywołanie out-of-line (które jest zwykle o rząd wielkości wolniejsze niż kod wbudowany dla pojedynczej operacji w którym liczy się szybkość przełączania), szczególnie jeśli przypadki nie są przypadkowo rozłożone na dużym obszarze. –

9

Jako pokarm do przemyśleń ... na wypadek, gdyby ktoś utknął w starym/wadliwym/nieefektywnym kompilatorze lub po prostu uwielbia hakować.

Wewnętrzna praca oświadczenia switch składa się z dwóch części. Znajdowanie adresu do skoku i dobrze skakać tam. W pierwszej części musisz użyć tabeli, aby znaleźć adres. Jeśli liczba przypadków wzrasta, stół staje się większy - wyszukiwanie adresu do skoku wymaga czasu. To właśnie kompilatory punktów starają się zoptymalizować, łącząc kilka technik, ale jednym prostym podejściem jest użycie tabeli bezpośrednio, która zależy od przestrzeni wartości wielkości liter.

Na odwrocie przykładu serwetki;

switch (n) { 
    case 1: foo(); break; 
    case 2: bar(); break; 
    case 3: baz(); break; 
} 

z takim kodem kompilatora kodu można utworzyć tablicę adresów_skrzyń i bezpośrednio uzyskać adres według tablicy [n]. Teraz szukanie zajęło O (1). Ale jeśli miał przełącznik jak poniżej:

switch (n) { 
    case 10: foo(); break; 
    case 17: bar(); break; 
    case 23: baz(); break; 
    // and a lot other 
} 

kompilator musi generować tabelę zawierającą case_id, jump_address par i kod do przeszukiwania tej struktury, która z najgorszych realizacji może trwać O (n). (Przyzwoite kompilatory optymalizują ten scenariusz, kiedy są w pełni uwolnieni dzięki włączeniu flag optymalizacyjnych w takim stopniu, że gdy zajdzie potrzeba debugowania takiego zoptymalizowanego kodu, twój mózg zacznie się smażyć).

To pytanie można zrobić wszystko na poziomie C, aby pokonać kompilator? i zabawne jest to, że podczas tworzenia tabel i przeszukiwania ich wydaje się łatwe, przeskakiwanie do zmiennej punktu przy użyciu goto nie jest możliwe w standardowym C. Istnieje więc szansa, że ​​jeśli nie zamierzasz używać wskaźników funkcji ze względu na obciążenie lub strukturę kodu, utkniesz ... cóż, jeśli nie używasz GCC. GCC ma niestandardową funkcję o nazwie Labels as Values, która pomaga uzyskać wskaźniki do etykiet.

Aby dokończyć przykład można napisać drugi switch z „etykiety jako wartości” funkcji tak:

const void *cases[] = {&&case_foo, &&case_bar, &&case_baz, ....}; 
goto *labels[n]; 
case_foo: 
    foo(); 
    goto switch_end; 
case_bar: 
    bar(); 
    goto switch_end; 
case_baz: 
    baz(); 
    goto switch_end; 
// and a lot other 
switch_end: 

oczywiście jeśli mówimy o 5000 przypadków, jest o wiele lepiej, jeśli napisać kawałek kodu, aby stworzyć ten kod dla ciebie - i prawdopodobnie jest to tylko sposób na utrzymanie takiego oprogramowania.

Jako uwagi końcowe; czy to poprawi twoją codzienną pracę? Nie. Czy to poprawi twoje umiejętności? Tak i rozmawiając z własnego doświadczenia, kiedyś przekonałem się, że poprawiłem algorytm bezpieczeństwa na karcie inteligentnej, właśnie poprzez optymalizację wielkości liter. To dziwny świat.

+1

dziękuję za odpowiedź, to nie jest część mojego projektu, ale ktoś zadał mi to pytanie i próbowałem wytłumaczyć go przy użyciu tablicy wskaźnika funkcji. Ale to nie była szybka droga. Ja sam szukałem szybszej drogi, nie mówię tu o czytelności czy designie. – MarsRover

0

Instrukcja dużego przełącznika, generalnie generowanego automatycznie, może wymagać długiego czasu kompilacji. Ale podoba mi się pomysł, że kompilator optymalizuje instrukcję switcha.

Jednym ze sposobów rozpadają instrukcji switch jest użycie bucketing,

int getIt(int input) 
{ 
    int bucket = input%16; 
    switch(bucket) 
    { 
    case 1: 
     return getItBucket1(input); 
    case 2: 
     return getItBucket2(input); 
    ... 
    ... 
    } 
    return -1; 
} 

Więc w kodzie powyżej, rozpadł się nasz instrukcji switch jest na 16 części. Łatwo zmienić liczbę segmentów w automatycznie generowanym kodzie.

Kod ten dodał koszt wykonania jednej warstwy pośredniej lub wywołania funkcji. . Ale biorąc pod uwagę kubełki zdefiniowane w różnych plikach, szybciej jest je skompilować równolegle.

Powiązane problemy