Haskell luồng heap tràn mặc dù chỉ sử dụng tổng bộ nhớ 22Mb?


114

Tôi đang cố gắng song song hóa một bộ dò tia. Điều này có nghĩa là tôi có một danh sách rất dài các phép tính nhỏ. Chương trình vani chạy trên một cảnh cụ thể trong 67,98 giây và 13 MB tổng bộ nhớ sử dụng và 99,2% năng suất.

Trong lần thử đầu tiên, tôi đã sử dụng chiến lược song song parBuffervới kích thước bộ đệm là 50. Tôi đã chọn parBuffervì nó lướt qua danh sách chỉ nhanh như tia lửa được tiêu thụ và không ép cột của danh sách parList, điều này sẽ sử dụng nhiều bộ nhớ vì danh sách rất dài. Với -N2, nó chạy trong thời gian 100,46 giây và 14 MB tổng bộ nhớ sử dụng và 97,8% năng suất. Thông tin tia lửa là:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)

Tỷ lệ lớn các tia lửa bị bắn ra cho thấy mức độ chi tiết của các tia lửa quá nhỏ, vì vậy tiếp theo tôi đã thử sử dụng chiến lược parListChunkchia danh sách thành nhiều phần và tạo ra tia lửa cho mỗi phần. Tôi đã nhận được kết quả tốt nhất với kích thước một đoạn là 0.25 * imageWidth. Chương trình chạy trong 93,43 giây và 236 MB tổng bộ nhớ sử dụng và 97,3% năng suất. Thông tin spark là: SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled). Tôi tin rằng việc sử dụng trí nhớ lớn hơn nhiều là bởi vì nó tạo parListChunknên cột sống của danh sách.

Sau đó, tôi cố gắng viết chiến lược của riêng mình để chia danh sách thành nhiều phần một cách lười biếng và sau đó chuyển các phần đến parBuffervà nối kết quả.

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

Điều này chạy trong 95,99 giây và 22MB tổng sử dụng bộ nhớ và 98,8% năng suất. Điều này thành công vì tất cả các tia lửa đang được chuyển đổi và việc sử dụng bộ nhớ thấp hơn nhiều, tuy nhiên tốc độ không được cải thiện. Đây là hình ảnh một phần của hồ sơ sự kiện.Hồ sơ nhật ký sự kiện

Như bạn có thể thấy các luồng đang bị dừng do tràn đống. Tôi đã thử thêm +RTS -M1Gđể tăng kích thước heap mặc định lên đến 1Gb. Kết quả không thay đổi. Tôi đọc rằng luồng chính của Haskell sẽ sử dụng bộ nhớ từ heap nếu ngăn xếp của nó bị tràn, vì vậy tôi cũng đã thử tăng kích thước ngăn xếp mặc định +RTS -M1G -K1Gnhưng điều này cũng không có tác động.

Có điều gì khác tôi có thể thử không? Tôi có thể đăng thông tin hồ sơ chi tiết hơn về việc sử dụng bộ nhớ hoặc nhật ký sự kiện nếu cần, tôi không bao gồm tất cả vì đó là rất nhiều thông tin và tôi không nghĩ rằng tất cả cần phải bao gồm.

CHỈNH SỬA: Tôi đã đọc về hỗ trợ đa lõi Haskell RTS và nó nói về việc có một HEC (Haskell Execution Context) cho mỗi lõi. Mỗi HEC chứa, trong số những thứ khác, một Khu vực phân bổ (là một phần của một đống được chia sẻ duy nhất). Bất cứ khi nào hết Khu vực Phân bổ của HEC, việc thu gom rác phải được thực hiện. Dường như là một tùy chọn RTS để kiểm soát nó, -A. Tôi đã thử -A32M nhưng không thấy sự khác biệt.

EDIT2: Đây là một liên kết đến một repo github dành riêng cho câu hỏi này . Tôi đã bao gồm các kết quả hồ sơ trong thư mục hồ sơ.

EDIT3: Đây là đoạn mã liên quan:

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)))

Các lưới là các phao ngẫu nhiên được colorPixel tính toán trước và sử dụng. Loại colorPixellà:

 colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color

2
Bạn có thể cung cấp cam kết chính xác mà bạn đã thử concat $ withStrategy …không? Tôi không thể tái tạo hành vi này 6008010, đó là cam kết gần nhất với bản chỉnh sửa của bạn.
Zeta

3
Tôi đã tạo một repo chuyên dụng để tôi không vô tình làm lộn xộn trong đó. Tôi cũng đã bao gồm tất cả các thông tin hồ sơ.
Justin Raymond

@dfeuer khi tôi nói rằng hãy xác định chiến lược của riêng mình, tôi không cố ý Strategy. Nên chọn một từ tốt hơn. Ngoài ra, các vấn đề tràn heap xảy ra với parListChunkparBufferquá.
Justin Raymond

Câu trả lời:


2

Không phải giải pháp cho vấn đề của bạn, mà là một gợi ý cho nguyên nhân:

Haskell dường như rất thận trọng trong việc tái sử dụng bộ nhớ và khi trình thông dịch nhận thấy tiềm năng lấy lại khối bộ nhớ, nó sẽ sử dụng nó. Mô tả vấn đề của bạn phù hợp với hành vi GC nhỏ được mô tả tại đây (dưới cùng) https://wiki.haskell.org/GHC/Memory_Management .

Dữ liệu mới được phân bổ trong "vườn ươm" 512kb. Khi nó cạn kiệt, "GC nhỏ" xảy ra - nó sẽ quét vườn ươm và giải phóng các giá trị không sử dụng.

Vì vậy, nếu bạn cắt dữ liệu thành các phần nhỏ hơn, bạn sẽ kích hoạt động cơ thực hiện dọn dẹp sớm hơn - GC sẽ khởi động.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.