2011-11-26 18 views
5

Jeśli tak, czy jest to część standardowej lub specjalnej optymalizacji ghc, na której możemy polegać? Lub po prostu optymalizacja, na którą nie zawsze możemy polegać.Czy ghc przekształca listę używaną tylko raz w generator ze względu na wydajność?

P.S .: Kiedy próbowałem próbki do badań, wydawało się wskazywać, że było to miejsce/

Prelude> let isOdd x = x `mod` 2 == 1 
Prelude> let isEven x = x `mod` 2 == 0 
Prelude> ((filter isOdd).(filter isEven)) [1..] 

Chews górę CPU ale nie zużywa dużo pamięci.

+2

Czy zdajesz sobie sprawę, że używasz interpretera do sprawdzania optymalizacji? – delnan

+1

Dobra rada, skompilowałem to, te same rzeczy się zdarzają –

+4

'filter odd. filtruj nawet $ [1 ..] ' – is7s

Odpowiedz

7

Zależy od tego, co rozumiemy przez generator. Lista jest generowana leniwie, a ponieważ nic innego nie wskazuje na to, zużyte części są prawie natychmiast zbierane. Ponieważ wynik powyższego obliczenia nie wzrasta, całe obliczenie przebiega w stałej przestrzeni. Nie jest to wymagane przez normę, ale ponieważ trudniej jest zaimplementować nieistotną semantykę o różnych zachowaniach przestrzennych dla tego przykładu (i wiele niejasno podobnych), w praktyce można na niej polegać.

Ale zwykle lista jest nadal generowana jako lista, więc powstało dużo śmieci. W sprzyjających warunkach ghc eliminuje listę [1 .. ] i tworzy pętlę dla realokacji:

result :: [Int] 
result = filter odd . filter even $ [1 .. ] 

(z wykorzystaniem funkcji Prelude OUT lenistwo), utworzonych z -O2 generuje rdzeń

List.result_go = 
    \ (x_ayH :: GHC.Prim.Int#) -> 
    case GHC.Prim.remInt# x_ayH 2 of _ { 
     __DEFAULT -> 
     case x_ayH of wild_Xa { 
      __DEFAULT -> List.result_go (GHC.Prim.+# wild_Xa 1); 
      9223372036854775807 -> GHC.Types.[] @ GHC.Types.Int 
     }; 
     0 -> 
     case x_ayH of wild_Xa { 
      __DEFAULT -> List.result_go (GHC.Prim.+# wild_Xa 1); 
      9223372036854775807 -> GHC.Types.[] @ GHC.Types.Int 
     } 
    } 

zwykły pętli , działa od 1 do maxBound :: Int, nie produkując niczego po drodze i [] na końcu. Jest prawie na tyle sprytny, aby powrócić do zwykłego powrotu: []. Zauważ, że istnieje tylko jeden podział o 2, GHC wie, że jeśli Int jest równy, nie może być nieparzysty, więc kontrola została wyeliminowana, aw żadnym oddziale nie jest tworzona niepusta lista (tj. Nieosiągalne gałęzie mają zostały usunięte przez kompilator).

+0

Jeśli zrobiłeś 'filter (\ x -> nieparzyste x && nawet x) [1 ..]" czy byłoby wystarczająco sprytne, aby jakoś zmienić lambdę w "const False"? – MatrixFrog

+1

@MatrixFrog Nie, jeszcze nie. Nie może zmienić '(\ x -> nieparzyste x && x)' na 'const False' ogólnie, ponieważ z dołu, musiałby to być' \ x -> seq x False' (1). W tym przypadku, dla 'Int' i kilku innych znanych typów, kompilator wie, że' [1 ..] 'nie zawiera dna, więc może, ale nie analizuje wystarczająco, aby połączyć te dwa elementy (Wątpię, aby taka sytuacja zdarzała się na tyle często, że taka analiza byłaby warta). (1) Dla niektórych znanych typów. Ogólnie rzecz biorąc, pozostały mod 2 musi zostać obliczony i porównany do 0, ponieważ obliczenia te mogą być nieterminacyjne. –

+0

@MatrixFrog W rzeczywistości istnieją doskonale uzasadnione przypadki, dla których ta optymalizacja byłaby błędna, nawet ignorując dna. Na przykład typ 'Expr' dostarczany przez pakiet' simple-reflect' łamie wiele "praw", które często zakładamy w instancjach 'Num' i' Integral' - takich jak ten. –

2

Ściśle mówiąc, Haskell nie określa żadnego konkretnego modelu oceny, więc implementacje mogą dowolnie implementować semantykę języka, tak jak chcą. Jednak w każdym rozsądnym wykonaniu, w tym GHC, możesz polegać na tym działaniu w stałej przestrzeni.

W GHC obliczenia takie jak te prowadzą do pojedynczo połączonej listy kończącej się odskokiem reprezentującym pozostałą część listy, która nie została jeszcze oceniona. Podczas oceny tej listy więcej list będzie generowanych na żądanie, ale od początku listy nie jest nigdzie indziej, wcześniejsze części są natychmiast kwalifikujące się do zbierania śmieci, więc otrzymujesz stałe zachowanie przestrzeni.

Po włączeniu optymalizacji GHC najprawdopodobniej przeprowadzi wylesianie w tym miejscu, optymalizując potrzebę posiadania listy, a wynikiem będzie prosta pętla bez przeprowadzonej alokacji.

+2

Pętla bez alokacji jest możliwa tylko w przypadku niektórych typów. Z 'Integer', jak byś otrzymał bez podpisu typu," licznik pętli "nadal musiałby być przydzielany przez stertę" Integer ". Główny punkt, eliminacja listy, oznacza. –

+0

Gdzie mogę przeczytać więcej o procesie deforestacji, o którym wspomniałeś? – haskelline

+0

@brence: Technika deforestacji używana obecnie przez GHC na listach nazywa się [foldr/build-fusion] (http: //www.haskell.org/haskellwiki/Correctness_of_short_cut_fusion # foldr.2Fbuild_4), podczas gdy inna forma o nazwie [stream fusion] (http://stackoverflow.com/questions/578063/what-is-haskells-stream-fusion) jest używana przez pakiet 'vector' . HaskellWiki posiada również [obszerną listę artykułów na temat różnych technik wylesiania] (http://www.haskell.org/haskellwiki/Research_papers/Compilation#Fusion_and_deforestation). To powinny być dobre miejsca do rozpoczęcia. – hammar

Powiązane problemy