Я намагаюся паралелізувати промінь-прослідковувач. Це означає, що у мене дуже довгий список невеликих обчислень. Програма ванілі працює на конкретній сцені за 67,98 секунд і 13 Мб загального використання пам'яті та 99,2% продуктивності.
У своїй першій спробі я застосував паралельну стратегію parBuffer
з розміром буфера 50. Я вибрав, parBuffer
тому що він проходить по списку так само швидко, як споживаються іскри, і не змушує хребта списку, як parList
, що використовує багато пам'яті оскільки список дуже довгий. З -N2
цим він працював за час 100,46 секунди та 14 Мб загального використання пам’яті та 97,8% продуктивності. Інформація про іскру:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)
Велика частка неясних іскор вказує на те, що зернистість іскор була занадто малою, тому далі я спробував використовувати стратегію parListChunk
, яка розбиває список на шматки і створює іскру для кожного шматка. Я отримав найкращі результати з розміром шматка 0.25 * imageWidth
. Програма працювала за 93,43 секунди та 236 Мб загального використання пам'яті та 97,3% продуктивності. Інформація іскра: SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
. Я вважаю, що набагато більше використання пам'яті відбувається тому, що parListChunk
змушує хребет списку.
Тоді я спробував написати власну стратегію, яка ліниво розділила список на шматки, а потім передала шматки parBuffer
і зв'язала результати.
concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map colorPixel pixels))
Це зайняло 95,99 секунд і 22 Мб загального використання пам’яті та 98,8% продуктивності. Це було успішним в тому сенсі, що всі іскри перетворюються і використання пам'яті значно нижче, проте швидкість не покращується. Ось зображення частини профілю eventlog.
Як видно, нитки зупиняються через переливи купи. Я спробував додати, +RTS -M1G
що збільшує розмір купи за замовчуванням до 1 Гбіт. Результати не змінилися. Я читав, що головний потік Haskell використовуватиме пам'ять з купи, якщо його стек переповнюється, тому я також спробував збільшити розмір стека за замовчуванням, +RTS -M1G -K1G
але це також не вплинуло.
Чи можна ще щось спробувати? Я можу розмістити більш детальну інформацію про профілювання для використання пам’яті чи журналу подій, якщо потрібно, я не включив її всю, тому що це багато інформації, і я не вважав, що все це потрібно включати.
EDIT: Я читав про багатоядерну підтримку Haskell RTS , і вона говорить про те, що для кожного ядра є HEC (Haskell Execution Context). Кожен HEC містить, між іншим, зону розподілу (яка є частиною однієї спільної купи). Щоразу, коли будь-яка територія розподілу HEC вичерпана, слід проводити збирання сміття. Здається, це параметр RTS для управління ним, -A. Я спробував -A32M, але не побачив різниці.
EDIT2: Ось посилання на репортаж github, присвячений цьому питанню . Я включив результати профілювання в папку профілювання.
EDIT3: Ось відповідний біт коду:
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)))
Сітки - це випадкові поплавці, які попередньо обчислюються та використовуються colorPixel. Тип colorPixel
:
colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color
Strategy
. Треба було підібрати краще слово. Також проблема переповнення купи виникає з parListChunk
і parBuffer
надто.
concat $ withStrategy …
? Я не можу відтворити таку поведінку6008010
, що є найближчим посиланням на вашу редакцію.