Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

switch ... case a if ... else ....

Ostatnio zmodyfikowano 2009-06-25 11:18
Autor Wiadomość
pompom
» 2009-06-24 09:34:34
Przecież napisałem wszystko w pseudokodzie...
Dobrze, napisałeś że umiesz assembler.

adresy dd _0, default, default, _3, default, default,default,default, _8, _8, default, default, _12, _13, default,
default, default, _17, _18, default, _20
;----------------------
mov eax, [liczba]
cmp eax, 20
ja default
jmp dword[adresy+eax*4]
_0: kod
_3: kod
_8: kod
_9: kod
_12: kod
_13: kod
_17: kod
_18: kod
_20: kod
default:
Tak wygląda typowy switch. Żadne wyszukiwanie logarytmiczne...
Można się o tym przekonać np. czytając kod wynikowy po kompilacji (wynik zależny od flag optymalizacyjnych, optymalizacja na rozmiar z małej ilości danych zrobi if..else if itd...).


Hm... wydawało mi się, że złożoności obliczeniowe wyraża się w liczbach całkowitych, a nie po przecinku... O(0,000000001)
To była ilość dodatkowych porównań, które byłyby potrzebne w switchu dla naprawdę rozstrzelonych danych, tak mała _średnia_ liczba porównań oznacza praktycznie stałą złożoność dla każdych danych.

Każda instrukcja to minimum 1 takt
Nieprawda, taki mov/movzw/movsw/neg/not/nop/add/sub/test/and/or/xor/cmp/dec/inc to 0.5 cykla (zależnie od modelu procesora).
Jeden argument jako pobranie wartości z adresu zazwyczaj dodaje jeden cykl.
fxch/fdecstp/fincstp/fldz zajmują... 0 cykli.

Dodatkowo nawet kilkanaście instrukcji jest wykonywane jednocześnie... ;)

Ale to tak btw...
P-7987
DejaVu
» 2009-06-24 13:33:36
http://www.iaii.we.po.opole.pl/dmdocuments/dyplomy/Arch_komp/cwicz1.html

Twoje rozumowanie sugeruje, iż wykonanie instrukcji fxch 10^1000 razy zajmie 0 milisekund :) co jest bzdurą :)
P-7994
DejaVu
» 2009-06-24 14:01:20
Zgodnie z tym co manfred mówi, switch ma złożoność liniową O(n), a nie jak można było przypuszczać logarytmiczną O(log(n)). Stałej złożoności nie uzyskasz. Zapewne za kilka lat switch będzie miał złożoność O(log(n)), jeśli ktoś tam z tych programistów od kompilatora 'zauważy', że można to zoptymalizować.
P-7996
pompom
» 2009-06-24 23:41:29
@manfred: A wygeneruj wartości po kolei. I co widzisz? No właśnie.
Gcc jest tutaj prosty:

Switch statements can be output in three forms.  A branch table is
   used if there are more than a few labels and the labels are dense
   within the range between the smallest and largest case value.  If a
   branch table is used, no further manipulations are done with the case
   node chain.

   The alternative to the use of a branch table is to generate a series
   of compare and jump insns.  When that is done, we use the LEFT, RIGHT,
   and PARENT fields to hold a binary tree.  Initially the tree is
   totally unbalanced, with everything on the right.  We balance the tree
   with nodes on the left having lower case values than the parent
   and nodes on the right having higher values.  We then output the tree
   in order.

   For very small, suitable switch statements, we can generate a series
   of simple bit test and branches instead.
(definicja gęstości zależy od flag optymalizacyjnych)
Visual C++ ma aż 5 trybów: jedna tablica, zwykłe if..else, wyszukiwanie binarne, wyszukiwanie binarne dla rozrzuconych wartości a później switche (kilka ciasnych grup), jeden switch a później porównania/switch dla reszty (to co napisałem).
Ostatnie jest najbardziej uniwersalne.

I jaki z tego wniosek?... Switch zawsze będzie albo szybszy, albo taki sam jak if..else. Niezależnie od kompilatora. Czyli dokładnie to co napisałem.
Złożoność jest stała. Tylko czasami stała złożoność jest wolniejsza (przez dużą tablicę) od porównań - i kompilator wybiera tą drugą. To takie trudne?

@Piotr Szawdyński: Nie, bo załadowanie pamięci z instrukcjami trochę zajmie. Dekodowanie też.
Strona, którą podałeś, ma czasy dla pentium1. Poczytaj "Intel Architecture Optimization Reference Manual".
fxch/fincstp/fdecstp nie są w ogóle wykonywane, są tylko brane pod uwagę przez dekoder.
fxch akurat zajmuje 0 cykli ale może być wykonany tylko raz na cykl (to znaczy że nie zabiera cykli innym instrukcjom), pozostałe dwie już bez ograniczeń.
P-8022
DejaVu
» 2009-06-25 03:24:40
Dobrze wiem, że są instrukcje, które można wykonać dodatkowo w trakcie życia jednego cyklu, jednak jak już napisałem tak czy siak musi istnieć cykl w którym instrukcja się wykona. Innymi słowy wszystkie polecenia, które podałeś potrzebują minimum jednego cyklu, żeby mogły zaistnieć. To, że dwie instrukcje potrafią się wykonać w jednym cyklu nie oznacza, że taka sytuacja będzie miała miejsce. Przykład już Ci podałem wystarczająco dobitnie 10^1000 ta sama instrukcja pod rząd.

/edit:
Ty zakładasz, że zawsze uzyskujesz optymistyczną złożoność, co w Twoim rozumowaniu oznacza jednoznacznie, że dla Ciebie QuickSort ma złożoność O(n). Takie rozumowanie jest błędne, jednak jeśli Ty tego nie widzisz to nie wprowadzaj chociaż innych w błąd.
P-8025
pompom
» 2009-06-25 05:40:06
kompilator sobie zrobi jumptabelę, jeśli wartości jest mało i są blisko siebie, w przeciwnym razie puści coś logarytmicznego/liniowego? Tak ogólnie to chyba nie rozumiesz złożoności - switch jako if..else if..else będzie liniowy w liczbę casów, w przypadku wyszukiwania binarnego logarytmiczny w liczbę casów.
Akurat odwrotnie, im więcej wartości tym częściej będzie tablica.
Wyszukiwanie binarne po wartościach jest realizowane przez zwykły if..else if.
A teraz, po polsku nie rozumiesz, to może matematycznie zrozumiesz:
n - ilość warunków
T(algorytm) - czas wykonania (ilość pamięci, instrukcji, optymalne wykorzystanie cache)
jeśli T(switch O(lg n)) < T(switch O(1))
to kompilator wygeneruje implementację z porównaniami, bo tak jest szybciej.
Z tego wynika że switch ma zawsze złożoność stałą i zawsze jest albo lepszy, albo taki sam jak ręcznie napisane ify.

Tak samo jak quicksort - zawsze O(n lg n). Jeśli podstawowa implementacja degeneruje się do n^2 (albo jest po prostu za wolny) następuje przełączanie na algorytm heapsort (albo inny).
Już jaśniej napisać nie można, jesli ciągle nie rozumiesz, nic na to nie poradzę. Dlatego z mojej strony eot.


Dobrze wiem, że są instrukcje, które można wykonać dodatkowo w trakcie życia jednego cyklu, jednak jak już napisałem tak czy siak musi istnieć cykl w którym instrukcja się wykona. Innymi słowy wszystkie polecenia, które podałeś potrzebują minimum jednego cyklu, żeby mogły zaistnieć. To, że dwie instrukcje potrafią się wykonać w jednym cyklu nie oznacza, że taka sytuacja będzie miała miejsce. Przykład już Ci podałem wystarczająco dobitnie 10^1000 ta sama instrukcja pod rząd.
Czas wykonania instrukcji to czas wykonania tej jednej instrukcji, nie załadowania pamięci do cache a później do dekodera - bo tego nie mierzy się w cyklach, tylko w ns/ms.
Jeśli będzie ta sama instrukcja pod rząd, procesor nie wykona nic. Cykl będzie pusty.

Sam sprawdź, intel podaje, że instrukcja zajmuje zawsze 0 cykli.
P-8026
DejaVu
» 2009-06-25 12:28:25
No to zrobię podsumowanie:
1) ja twierdziłem, że złożoność pesymistyczna switch'a jest O(log(n)). Po testach, które przeprowadził manfred okazało się, że o dziwo złożoność switcha jest O(n) - jest na to namacalny dowód.
2) manfred - twierdzi, że złożoność pesymistyczna switch'a jest liniowa, czyli O(n). Wykonał do tego testy, przedstawił argumenty i poparł to de'facto dowodami.
3) pompom - twierdzi, że złożoność pesymistyczna switch'a jest zawsze stała O(1), na co przytaczał różne argumenty.

Konkludując wątek - myślę, że prawda może leżeć po środku, czyli dla wyboru wartości, która nie istnieje w switch'u:
1) Optymistyczna wersja: złożoność switch'a jest O(1), gdy będą ustawione flagi optymalizacyjne (Visual C++ 2005) i wartości w switch'u nie będą przypadkowe i będą one blisko siebie.
2) Pesymistyczna wersja: złożoność switch'a jest O(n), czyli jest zamieniana de'facto na if...else. Wersja ta została potwierdzona przez manfred'a na kompilatorze GCC jak i na Visual C++ 2008.
3) To co wynika ze zdrowego rozsądku: złożoność switcha w najgorszym przypadku powinna być O(log(n)) - czyli zbudowane zrównoważone drzewo binarne. Okazało się jednak, że ten przypadek nie zaszedł nigdy w praktyce.

Na koniec dodam, że w algorytmach wnętrze pętli przyjmuje się, że ma stałą złożoność O(1) chyba, że występują tam jawnie pętle. Tak więc złożoność obliczeniowa switch'a nie ma istotnego znaczenia z praktycznego punktu widzenia, tj. z punktu widzenia złożoności obliczeniowych algorytmów.
P-8031
manfred
» 2009-06-23 11:08:31
podczas gdy switch - zawsze stałą
Tja, dobre :D. Wiesz, że dzwoni, ale nie wiesz w którym kościele. Switch ma złożoność stałą w nielicznych przypadkach, kiedy wartości skoków są blisko siebie, w przeciwnym razie jest logarytmiczna (wyszukiwanie binarne) / liniowa (przerabiany na if..else if..else). A czytelności to mu zarzucić nie można.
P-18981
1 « 2 » 3
Poprzednia strona Strona 2 z 3 Następna strona