เธรด Haskell ล้นแม้จะใช้หน่วยความจำทั้งหมดเพียง 22Mb?


114

ฉันกำลังพยายามขนานเรย์ - เทรเซอร์ ซึ่งหมายความว่าฉันมีรายการการคำนวณขนาดเล็กที่ยาวมาก โปรแกรมวานิลลาทำงานบนฉากเฉพาะใน 67.98 วินาทีและ 13 MB ของการใช้หน่วยความจำทั้งหมดและผลผลิต 99.2%

ในความพยายามครั้งแรกของฉันฉันใช้กลยุทธ์คู่ขนานที่parBufferมีขนาดบัฟเฟอร์ 50 ฉันเลือกparBufferเพราะมันเดินผ่านรายการได้เร็วที่สุดเท่าที่ประกายไฟจะหมดและไม่บังคับกระดูกสันหลังของรายการเช่นparListซึ่งจะใช้หน่วยความจำมาก เนื่องจากรายการยาวมาก ด้วย-N2มันทำงานในเวลา 100.46 วินาทีและ 14 MB ของการใช้หน่วยความจำทั้งหมดและผลผลิต 97.8% ข้อมูลจุดประกายคือ:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

ประกายไฟที่มอดลงในสัดส่วนที่มากบ่งบอกว่าความละเอียดของประกายไฟมีขนาดเล็กเกินไปดังนั้นต่อไปฉันจึงลองใช้กลยุทธ์parListChunkซึ่งแยกรายการออกเป็นชิ้น ๆ และสร้างประกายสำหรับแต่ละชิ้น ฉันได้ผลลัพธ์ที่ดีที่สุดด้วยขนาดของ0.25 * imageWidth. โปรแกรมทำงานใน 93.43 วินาทีและ 236 MB ของการใช้หน่วยความจำทั้งหมดและประสิทธิภาพการผลิต 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 วินาทีและ 22MB ของการใช้หน่วยความจำทั้งหมดและประสิทธิภาพการทำงาน 98.8% สิ่งนี้ประสบความสำเร็จในแง่ที่ว่าประกายไฟทั้งหมดกำลังถูกแปลงและการใช้หน่วยความจำต่ำกว่ามากอย่างไรก็ตามความเร็วไม่ได้รับการปรับปรุง นี่คือภาพส่วนหนึ่งของโปรไฟล์ eventlogโปรไฟล์บันทึกเหตุการณ์

ดังที่คุณเห็นว่าเธรดกำลังหยุดทำงานเนื่องจากฮีปล้น ฉันพยายามเพิ่ม+RTS -M1Gซึ่งจะเพิ่มขนาดฮีปเริ่มต้นจนถึง 1Gb ผลลัพธ์ไม่เปลี่ยนแปลง ฉันอ่านว่าเธรดหลักของ Haskell จะใช้หน่วยความจำจากฮีปหากสแต็กล้นดังนั้นฉันจึงพยายามเพิ่มขนาดสแต็กเริ่มต้นด้วย+RTS -M1G -K1Gแต่สิ่งนี้ก็ไม่มีผลกระทบเช่นกัน

มีอะไรให้ลองทำอีกไหม? ฉันสามารถโพสต์ข้อมูลการทำโปรไฟล์โดยละเอียดเพิ่มเติมสำหรับการใช้หน่วยความจำหรือบันทึกเหตุการณ์ได้หากจำเป็นฉันไม่ได้รวมไว้ทั้งหมดเนื่องจากเป็นข้อมูลจำนวนมากและฉันไม่คิดว่าจำเป็นต้องรวมไว้ทั้งหมด

แก้ไข: ฉันกำลังอ่านเกี่ยวกับการสนับสนุนมัลติคอร์ของHaskell RTSและพูดถึงการมี HEC (Haskell Execution Context) สำหรับแต่ละคอร์ HEC แต่ละตัวประกอบด้วยพื้นที่จัดสรร (ซึ่งเป็นส่วนหนึ่งของฮีปที่แบ่งใช้ร่วมกัน) เมื่อใดก็ตามที่พื้นที่จัดสรรของ HEC หมดลงจะต้องดำเนินการจัดเก็บขยะ ดูเหมือนจะเป็นตัวเลือก RTSเพื่อควบคุม -A ฉันลอง -A32M แต่ไม่เห็นความแตกต่าง

EDIT2: นี่คือการเชื่อมโยงไป GitHub Repo ทุ่มเทให้กับคำถามนี้ ฉันได้รวมผลลัพธ์การทำโปรไฟล์ไว้ในโฟลเดอร์การทำโปรไฟล์แล้ว

แก้ไข 3: นี่คือบิตโค้ดที่เกี่ยวข้อง:

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

2
คุณสามารถระบุการกระทำที่คุณพยายามได้concat $ withStrategy …หรือไม่? ฉันไม่สามารถสร้างพฤติกรรมนี้ซ้ำได้ 6008010ซึ่งเป็นการกระทำที่ใกล้เคียงที่สุดกับการแก้ไขของคุณ
Zeta

3
ฉันสร้าง repo โดยเฉพาะดังนั้นฉันจึงไม่เผลอไปยุ่งกับมัน ฉันได้รวมข้อมูลการทำโปรไฟล์ทั้งหมดไว้ด้วย
Justin Raymond

@dfeuer Strategyเมื่อฉันกล่าวว่าการกำหนดกลยุทธ์ของฉันเองฉันไม่ได้หมายความว่า ควรเลือกคำที่ดีกว่านี้ นอกจากนี้ยังมีปัญหากองล้นเกิดขึ้นกับparListChunkและparBufferมากเกินไป
Justin Raymond

คำตอบ:


2

ไม่ใช่วิธีแก้ปัญหาของคุณ แต่เป็นคำแนะนำถึงสาเหตุ:

ดูเหมือนว่า Haskell จะระมัดระวังในการนำหน่วยความจำกลับมาใช้ใหม่และเมื่อล่ามเห็นศักยภาพในการเรียกคืนบล็อกหน่วยความจำก็จะดำเนินการต่อไป คำอธิบายปัญหาของคุณเหมาะกับพฤติกรรมเล็ก ๆ น้อย ๆ GC ที่อธิบายที่นี่ (ล่าง) https://wiki.haskell.org/GHC/Memory_Management

ข้อมูลใหม่ได้รับการจัดสรรใน 512kb "สถานรับเลี้ยงเด็ก" เมื่อหมดแล้ว "minor GC" จะเกิดขึ้น - จะสแกนเนอสเซอรี่และปลดปล่อยค่าที่ไม่ได้ใช้

ดังนั้นหากคุณสับข้อมูลเป็นชิ้นเล็ก ๆ คุณจะเปิดใช้งานเครื่องยนต์เพื่อทำการล้างข้อมูลก่อนหน้านี้ - GC เข้ามา

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.