Przepełnienie sterty wątków Haskell pomimo tylko 22Mb całkowitego zużycia pamięci?

114

Próbuję zrównoleglenie ray-tracera. Oznacza to, że mam bardzo długą listę małych obliczeń. Program podstawowy działa na określonej scenie w 67,98 sekund i 13 MB całkowitego wykorzystania pamięci oraz 99,2% wydajności.

W mojej pierwszej próbie użyłem strategii równoległej parBufferz rozmiarem bufora 50. Wybrałem, parBufferponieważ przegląda listę tylko tak szybko, jak zużywane są iskry, i nie wymusza na jej grzbiecie parList, co wymagałoby dużo pamięci ponieważ lista jest bardzo długa. Z -N2tym działał w czasie 100,46 sekundy i 14 MB całkowitego wykorzystania pamięci oraz 97,8% wydajności. Informacje o iskrze to:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

Duża część iskier syczących wskazuje, że ziarnistość iskier była zbyt mała, więc następnie spróbowałem użyć strategii parListChunk, która dzieli listę na kawałki i tworzy iskry dla każdego fragmentu. Najlepsze wyniki uzyskałem przy wielkości porcji 0.25 * imageWidth. Program działał w 93,43 sekundy przy 236 MB całkowitego wykorzystania pamięci i 97,3% wydajności. Informacje iskra jest: SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled). Uważam, że dużo większe użycie pamięci wynika z tego, że parListChunkwymusza kręgosłup listy.

Potem spróbowałem napisać własną strategię, która leniwie dzieliła listę na części, a następnie przekazywała je do parBufferi konkatenowała wyniki.

 concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map colorPixel pixels))

Trwało to 95,99 sekund i łącznie zajmowało 22 MB pamięci oraz 98,8% wydajności. Udało się to w tym sensie, że wszystkie iskry są konwertowane, a zużycie pamięci jest znacznie niższe, jednak prędkość nie ulega poprawie. Oto obraz części profilu eventlog.Profil dziennika zdarzeń

Jak widać, wątki są zatrzymywane z powodu przepełnienia sterty. Próbowałem dodać, +RTS -M1Gco zwiększa domyślny rozmiar sterty aż do 1 GB. Wyniki się nie zmieniły. Czytałem, że główny wątek Haskell będzie używał pamięci ze sterty, jeśli jego stos się przepełni, więc próbowałem również zwiększyć domyślny rozmiar stosu, +RTS -M1G -K1Gale to również nie miało wpływu.

Czy jest coś jeszcze, co mogę spróbować? W razie potrzeby mogę zamieścić bardziej szczegółowe informacje o profilowaniu wykorzystania pamięci lub dzienniku zdarzeń, nie uwzględniłem tego wszystkiego, ponieważ jest to dużo informacji i nie sądziłem, że wszystkie muszą być uwzględnione.

EDYCJA: Czytałem o obsłudze wielu rdzeni Haskell RTS i mówi o istnieniu HEC (Haskell Execution Context) dla każdego rdzenia. Każdy HEC zawiera, między innymi, obszar alokacji (który jest częścią jednego wspólnego stosu). Ilekroć którykolwiek obszar alokacji HEC jest wyczerpany, należy przeprowadzić czyszczenie pamięci. Wygląda na to, że jest to opcja RTS do sterowania nią, -A. Próbowałem -A32M, ale nie widziałem różnicy.

EDIT2: Oto link do repozytorium github poświęconego temu pytaniu . Wyniki profilowania umieściłem w folderze profilowania.

EDIT3: Oto odpowiedni bit kodu:

render :: [([(Float,Float)],[(Float,Float)])] -> World -> [Color]
render grids world = cs where 
  ps = [ (i,j) | j <- reverse [0..wImgHt world - 1] , i <- [0..wImgWd world - 1] ]
  cs = map (colorPixel world) (zip ps grids)
  --cs = withStrategy (parListChunk (round (wImgWd world)) rdeepseq) (map (colorPixel world) (zip ps grids))
  --cs = withStrategy (parBuffer 16 rdeepseq) (map (colorPixel world) (zip ps grids))
  --cs = concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map (colorPixel world) (zip ps grids)))

Siatki są losowe pływaki, które są wykorzystywane przez precomputed i typu colorPixel.The z colorPixelIS:

 colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color
Justin Raymond
źródło
2
Czy możesz podać dokładne zatwierdzenie, w którym próbowałeś concat $ withStrategy …? Nie mogę odtworzyć tego zachowania w programie 6008010, który jest najbliższym zatwierdzeniem Twojej zmiany.
Zeta
3
Zrobiłem dedykowane repozytorium, żeby przypadkowo się w nim nie bałaganić. Zawarłem również wszystkie informacje dotyczące profilowania.
Justin Raymond
@dfeuer, kiedy powiedziałem zdefiniuj własną strategię, nie miałem na myśli Strategy. Powinienem był wybrać lepsze słowo. Ponadto występuje problem przepełnienia sterty w programie parListChunki parBuffer.
Justin Raymond

Odpowiedzi:

2

Nie rozwiązanie twojego problemu, ale wskazówka co do przyczyny:

Haskell wydaje się być bardzo konserwatywny w ponownym wykorzystaniu pamięci, a kiedy tłumacz widzi możliwość odzyskania bloku pamięci, robi to. Opis Twojego problemu pasuje do pomniejszego zachowania GC opisanego tutaj (na dole) https://wiki.haskell.org/GHC/Memory_Management .

Nowe dane są przydzielane w 512kb "żłobku". Po wyczerpaniu pojawia się „pomniejsze GC” - skanuje żłobek i uwalnia niewykorzystane wartości.

Więc jeśli podzielisz dane na mniejsze fragmenty, włączysz silnik do wcześniejszego czyszczenia - włącza się GC.

Thinkeye
źródło