2012-03-15 16 views
15

Istnieje tutaj podstawowe pytanie dotyczące monady, niezwiązane z Repa, oraz kilka pytań specyficznych dla Repa.Repa 3 wydajność i poprawne użycie "teraz"

Pracuję nad biblioteką przy użyciu Repa3. Mam problem z uzyskaniem wydajnego kodu równoległego. Jeśli sprawię, że moje funkcje zwrócą tablice opóźnione, otrzymuję niesamowicie powolny kod, który skaluje się bardzo dobrze do 8 rdzeni. Ten kod zajmuje ponad 20 GB pamięci na profiler GHC i przebiega o kilka rzędów wielkości wolniej niż podstawowe nieskrytowane wektory Haskella. Alternatywnie, jeśli wszystkie moje funkcje zwrócą się do niezapakowanych tablic manifestów (nadal próbuję użyć fuzji w ramach funkcji, na przykład gdy wykonuję "mapę"), otrzymam DUŻO szybszego kodu (nadal wolniej niż przy użyciu rozpakowanego Haskella). wektory), które nie skalują się w ogóle, a w rzeczywistości wydają się nieco wolniejsze z większą liczbą rdzeni.

W oparciu o przykładowy kod FFT w algorytmach Repa, wydaje się, że właściwym podejściem jest zawsze zwracanie manifestu tablic. Czy jest kiedykolwiek przypadek, w którym powinienem zwrócić opóźnione tablice?

Kod FFT umożliwia także korzystanie z funkcji "teraz". Jednak pojawia się błąd typu, gdy próbuję go użyć w moim kodzie:

type Arr t r = Array t DIM1 r 
data CycRingRepa m r = CRTBasis (Arr U r) 
        | PowBasis (Arr U r) 

fromArray :: forall m r t. (BaseRing m r, Unbox r, Repr t r) => Arr t r -> CycRingRepa m r 
fromArray = 
    let mval = reflectNum (Proxy::Proxy m) 
    in \x -> 
     let sh:.n = extent x 
     in assert (mval == 2*n) PowBasis $ now $ computeUnboxedP $ bitrev x 

Kod kompiluje dobrze bez "teraz". Z „teraz”, pojawia się następujący błąd:

Couldn't match type r' with Array U (Z :. Int) r' `r' is a rigid type variable bound by the type signature for fromArray :: (BaseRing m r, Unbox r, Repr t r) => Arr t r -> CycRingRepa m r at C:\Users\crockeea\Documents\Code\LatticeLib\CycRingRepa.hs:50:1 Expected type: CycRingRepa m r Actual type: CycRingRepa m (Array U DIM1 r)

ja nie myślećthis jest mój problem. Byłoby pomocne, gdyby ktoś mógł wyjaśnić, w jaki sposób Monada działa w "teraz". Według mojej najlepszej oceny, monada wydaje się tworzyć "Arr U (Arr U r)". Spodziewam się "Arr U r", który będzie wtedy pasował do wzorca konstruktora danych. Co się dzieje i jak to naprawić?

Podpisy typu są:

computeUnboxedP :: Fill r1 U sh e => Array r1 sh e -> Array U sh e 
now :: (Shape sh, Repr r e, Monad m) => Array r sh e -> m (Array r sh e) 

Byłoby pomocne mieć lepsze wyobrażenie o tym, kiedy należy użyć „teraz”.

Kilka innych pytań Repa: Czy mogę jawnie wywołać computeUnboxedP (jak w przykładowym kodzie FFT), czy też powinienem użyć bardziej ogólnego computeP (ponieważ część unbox jest wywnioskowana według mojego typu danych)? Czy należy przechowywać opóźnione lub jawne tablice w typie danych CycRingRepa? Ostatecznie chciałbym również, aby ten kod działał z Integerami Haskella. Czy będzie to wymagało napisania nowego kodu, który będzie wykorzystywał coś innego niż tablice U, czy mogę napisać kod polimorficzny, który tworzy tablice U dla typów unbox i jakiejś innej tablicy dla liczb całkowitych/typów pudełkowych?

Zdaję sobie sprawę, że jest tu wiele pytań i doceniam wszystkie/wszystkie odpowiedzi!

Odpowiedz

2

Repa 3.1 nie wymaga już explicite użycia now. Równoległe funkcje obliczeniowe są monadyczne i automatycznie aplikują deepSeqArray do ich wyników. Pakiet repa-examples zawiera również nową implementację macierzy pomnożonej, która demonstruje ich użycie.

8

Oto kod źródłowy now:

now arr = do 
    arr `deepSeqArray` return() 
    return arr 

Więc to naprawdę tylko monadycznego wersja deepSeqArray. Możesz użyć jednego z nich, aby wymusić ocenę, zamiast trzymać się thunk. Ta "ewaluacja" różni się od "wymuszenia" wymuszonego, gdy wywoływana jest computeP.

W Twoim kodzie now nie ma zastosowania, ponieważ nie jesteś w monadzie. Ale w tym kontekście nie pomogłoby też deepSeqArray.Rozważmy taką sytuację:

x :: Array U Int Double 
x = ... 

y :: Array U Int Double 
y = computeUnboxedP $ map f x 

Od y dotyczy x, chcielibyśmy mieć pewność x jest obliczane przed rozpoczęciem obliczyć y. Jeśli nie, dostępna praca nie zostanie poprawnie rozłożona na grupę wątków. Aby uzyskać to wyszło, że lepiej napisać y jak

y = deepSeqArray x . computeUnboxedP $ map f x 

Teraz, opóźnionego tablicy mamy

deepSeqArray (ADelayed sh f) y = sh `deepSeq` f `seq` y 

Zamiast obliczania wszystkie elementy, to właśnie sprawia, że ​​pewien kształt jest obliczyć i zmniejszyć f do postaci normalnej słabej głowy.

Jeśli chodzi o tablice manifestu vs opóźnione, to z pewnością preferowane są tablice z opóźnionym czasem.

Tutaj "rozszerzenie" generuje nową tablicę, kopiując wartości w pewnym zestawie nowych wymiarów. W szczególności oznacza to, że

arrRepl ! (Z :. i :. j :. k) == arrRepl ! (Z :. i :. j' :. k) 

szczęście extend produkuje opóźnione tablicę, ponieważ byłoby to marnotrawstwo przejść przez kłopotów z tym wszystkim kopiowaniem.

Opóźnione tablice pozwalają również na fuzję, co jest niemożliwe, jeśli tablica jest zamanifestowana.

Wreszcie computeUnboxedP jest po prostu computeP z wyspecjalizowanym typem. Nadanie wartości computeUnboxedP jawnie może pozwolić GHC na lepszą optymalizację i sprawia, że ​​kod jest trochę jaśniejszy.

Powiązane problemy