11

Mam tę funkcję Haskella, która powoduje ponad 50% wszystkich alokacji mojego programu, powodując, że 60% mojego czasu pracy jest pobierane przez GC . Używam małego stosu (-K10K), więc nie ma przepełnienia stosu, ale czy mogę uczynić tę funkcję szybszą, z mniejszą alokacją?Zoptymalizuj funkcję listy, która tworzy zbyt dużo śmieci (nie przepełnienie stosu)

Celem jest obliczenie iloczynu macierzy przez wektor. Nie mogę użyć na przykład hmatrix, ponieważ jest to część większej funkcji przy użyciu pakietu ad Automatic Differentiation, więc potrzebuję użyć list z Num. W czasie pracy sądzę, że użycie modułu Numeric.AD oznacza, że ​​moje typy muszą być Scalar Double.

listMProd :: (Num a) => [a] -> [a] -> [a] 
listMProd mdt vdt = go mdt vdt 0 
    where 
    go [] _ s = [s] 
    go ls [] s = s : go ls vdt 0 
    go (y:ys) (x:xs) ix = go ys xs (y*x+ix) 

Zasadniczo pętla poprzez mnożenie macierzy i dodanie akumulator, aż do osiągnięcia końca wektorze przechowywania wyników, a dalej znowu ponownym wektor. Mam test quickcheck sprawdzający, czy otrzymuję taki sam wynik, jak produkt macierzy/wektor w hmatrix.

Próbowałem z foldl, foldr, itp. Nic nie próbowałem sprawia, że ​​funkcja jest szybsza (i niektóre rzeczy jak foldr powodują wyciek przestrzeni).

Biegając z profilowaniem mówi mi, oprócz faktu, że ta funkcja jest gdzie większość czasu spędza się i przydziału, że istnieje mnóstwo Cells tworzone, Cells jest typ danych z pakietu ad.

Prosty test do uruchomienia:

import Numeric.AD 

main = do 
    let m :: [Double] = replicate 400 0.2 
     v :: [Double] = replicate 4 0.1 
     mycost v m = sum $ listMProd m v 
     mygrads = gradientDescent (mycost (map auto v)) (map auto m) 
    print $ mygrads !! 1000 

to na moim komputerze mówi mi GC jest zajęty 47% czasu.

Wszelkie pomysły?

+1

Więcej informacji! Jak uruchamiasz ten program? Gdzie jest twoja uprząż testowa? Jakie konkretne typy używasz? Jakie są flagi i wersja twojego kompilatora Haskell? –

+0

Dodano trochę informacji. Ta funkcja jest wywoływana za pośrednictwem funkcji ad grad, która wykorzystuje własne typy (wystąpienia Num). Profilowanie pokazuje alokacje "Komórek". –

+0

Kilka naiwnych sugestii: czy rozważałeś użycie stanu zmiennego z 'ST'? I [stream-fusion] (https://hackage.haskell.org/package/stream-fusion-0.1.2.5/docs/Data-List-Stream.html)/[conduit] (https: //hackage.haskell. org/package/conduit)/[pipes] (https://hackage.haskell.org/package/pipes)? Może (?) Może nawet warto przekształcić twoją listę wektorów w coś innego, np. a [unboxed vector] (http://lambda.jstolarek.com/2013/04/haskell-as-fast-as-c-a-case-study/)? Nie mam doświadczenia z żadną z tych technik, ale może linki mogą ci pomóc dalej. –

Odpowiedz

7

Bardzo prosta optymalizacja jest, aby funkcja go ścisłe jego parametrów akumulatora, ponieważ jest niewielki, może być unboxed jeśli a jest prymitywny i zawsze musi być w pełni ocenione:

{-# LANGUAGE BangPatterns #-} 
listMProd :: (Num a) => [a] -> [a] -> [a] 
listMProd mdt vdt = go mdt vdt 0 
    where 
    go [] _ !s = [s] 
    go ls [] !s = s : go ls vdt 0 
    go (y:ys) (x:xs) !ix = go ys xs (y*x+ix) 

Na moim komputerze, daje przyspieszenie 3-4x (skompilowane z -O2).

Z drugiej strony listy pośrednie nie powinny być ścisłe, aby można je było łączyć.

+0

Mhh, dobry pomysł, ale to wcale nie pomaga w moim przypadku użycia (brak poprawy prędkości lub wykorzystania GC). Wydaje mi się, że to, co wywołuje ta funkcja za pośrednictwem biblioteki reklam, wpływa na wydajność (widzę typ danych w komórkach ze ścisłym polem Int). –

Powiązane problemy