Tôi đang cố gắng hiểu một sợi dây màu xanh lá cây đắt tiền trong Haskell (GHC 7.10.1 trên OS X 10.10.5) thực sự là như thế nào. Tôi biết rằng siêu rẻ của nó so với một chuỗi hệ điều hành thực sự, cho cả việc sử dụng bộ nhớ và cho CPU.Haskell/GHC cho mỗi chi phí bộ nhớ luồng
Phải, vì vậy tôi bắt đầu viết một chương trình siêu đơn giản với nhánh n
(xanh lá cây) chủ đề (sử dụng thư viện async
xuất sắc) và sau đó chỉ ngủ mỗi sợi trong m
giây.
Vâng, đó là dễ dàng đủ:
$ cat PerTheadMem.hs
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (mapConcurrently)
import System.Environment (getArgs)
main = do
args <- getArgs
let (numThreads, sleep) = case args of
numS:sleepS:[] -> (read numS :: Int, read sleepS :: Int)
_ -> error "wrong args"
mapConcurrently (\_ -> threadDelay (sleep*1000*1000)) [1..numThreads]
và trước hết, chúng ta hãy biên dịch và chạy nó:
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.10.1
$ ghc -rtsopts -O3 -prof -auto-all -caf-all PerTheadMem.hs
$ time ./PerTheadMem 100000 10 +RTS -sstderr
rằng nên ngã ba 100k đề và chờ 10s trong mỗi và sau đó in chúng tôi một số thông tin:
$ time ./PerTheadMem 100000 10 +RTS -sstderr
340,942,368 bytes allocated in the heap
880,767,000 bytes copied during GC
164,702,328 bytes maximum residency (11 sample(s))
21,736,080 bytes maximum slop
350 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 648 colls, 0 par 0.373s 0.415s 0.0006s 0.0223s
Gen 1 11 colls, 0 par 0.298s 0.431s 0.0392s 0.1535s
INIT time 0.000s ( 0.000s elapsed)
MUT time 79.062s (92.803s elapsed)
GC time 0.670s ( 0.846s elapsed)
RP time 0.000s ( 0.000s elapsed)
PROF time 0.000s ( 0.000s elapsed)
EXIT time 0.065s ( 0.091s elapsed)
Total time 79.798s (93.740s elapsed)
%GC time 0.8% (0.9% elapsed)
Alloc rate 4,312,344 bytes per MUT second
Productivity 99.2% of total user, 84.4% of total elapsed
real 1m33.757s
user 1m19.799s
sys 0m2.260s
Phải mất khá lâu (1m33,757s) cho mỗi chủ đề được cho là chỉ chỉ đợi 10 giây nhưng chúng tôi đã xây dựng nó không phải là luồng đủ công bằng cho đến bây giờ. Tất cả trong tất cả, chúng tôi sử dụng 350 MB, đó không phải là quá xấu, đó là 3,5 KB cho mỗi chủ đề. Cho rằng kích thước ngăn xếp ban đầu (-ki
is 1 KB).
Đúng vậy, nhưng bây giờ chúng ta hãy biên dịch ở chế độ ren và xem liệu chúng ta có thể nhận được bất kỳ nhanh hơn:
$ ghc -rtsopts -O3 -prof -auto-all -caf-all -threaded PerTheadMem.hs
$ time ./PerTheadMem 100000 10 +RTS -sstderr
3,996,165,664 bytes allocated in the heap
2,294,502,968 bytes copied during GC
3,443,038,400 bytes maximum residency (20 sample(s))
14,842,600 bytes maximum slop
3657 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 6435 colls, 0 par 0.860s 1.022s 0.0002s 0.0028s
Gen 1 20 colls, 0 par 2.206s 2.740s 0.1370s 0.3874s
TASKS: 4 (1 bound, 3 peak workers (3 total), using -N1)
SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
INIT time 0.000s ( 0.001s elapsed)
MUT time 0.879s ( 8.534s elapsed)
GC time 3.066s ( 3.762s elapsed)
RP time 0.000s ( 0.000s elapsed)
PROF time 0.000s ( 0.000s elapsed)
EXIT time 0.074s ( 0.247s elapsed)
Total time 4.021s (12.545s elapsed)
Alloc rate 4,544,893,364 bytes per MUT second
Productivity 23.7% of total user, 7.6% of total elapsed
gc_alloc_block_sync: 0
whitehole_spin: 0
gen[0].sync: 0
gen[1].sync: 0
real 0m12.565s
user 0m4.021s
sys 0m1.154s
Wow, nhiều nhanh hơn, chỉ cần 12s bây giờ, cách tốt hơn. Từ Activity Monitor, tôi thấy rằng nó đã sử dụng 4 chủ đề OS cho các chuỗi màu xanh lá cây 100k, điều này có ý nghĩa.
Tuy nhiên, 3657 MB tổng bộ nhớ! Hơn 10 lần so với phiên bản không phải là luồng được sử dụng ...
Cho đến bây giờ, tôi không làm bất kỳ hồ sơ nào bằng cách sử dụng -prof
hoặc -hy
hoặc hơn thế. Để điều tra thêm một chút, tôi đã thực hiện một số hồ sơ heap (-hy
) trong riêng biệt chạy. Việc sử dụng bộ nhớ không thay đổi trong cả hai trường hợp, các biểu đồ heap hồ sơ trông thú vị khác nhau (trái: không phải luồng, phải: luồng) nhưng tôi không thể tìm thấy lý do cho sự khác biệt 10x.
Khác với đầu ra hồ sơ (.prof
tệp) Tôi cũng không tìm thấy bất kỳ sự khác biệt thực sự nào.
Vì vậy, câu hỏi của tôi: Sự chênh lệch 10x trong việc sử dụng bộ nhớ đến từ đâu?
CHỈNH SỬA: Chỉ cần đề cập đến: Sự khác biệt tương tự được áp dụng khi chương trình thậm chí không được biên dịch với hỗ trợ lược tả. Vì vậy, chạy time ./PerTheadMem 100000 10 +RTS -sstderr
với ghc -rtsopts -threaded -fforce-recomp PerTheadMem.hs
là 3559 MB. Và với ghc -rtsopts -fforce-recomp PerTheadMem.hs
là 395 MB.
EDIT 2: Trên Linux (GHC 7.10.2
trên Linux 3.13.0-32-generiC#57-Ubuntu SMP, x86_64
) cùng xảy ra: Non-ren 460 MB trong 1m28.538s và ren là 3483 MB là 12.604s. /usr/bin/time -v ...
báo cáo Maximum resident set size (kbytes): 413684
và Maximum resident set size (kbytes): 1645384
tương ứng.
EDIT 3: Cũng thay đổi chương trình để sử dụng forkIO
trực tiếp:
import Control.Concurrent (threadDelay, forkIO)
import Control.Concurrent.MVar
import Control.Monad (mapM_)
import System.Environment (getArgs)
main = do
args <- getArgs
let (numThreads, sleep) = case args of
numS:sleepS:[] -> (read numS :: Int, read sleepS :: Int)
_ -> error "wrong args"
mvar <- newEmptyMVar
mapM_ (\_ -> forkIO $ threadDelay (sleep*1000*1000) >> putMVar mvar())
[1..numThreads]
mapM_ (\_ -> takeMVar mvar) [1..numThreads]
Và nó không thay đổi bất cứ điều gì: không ren: 152 MB, luồng: 3308 MB.
Tôi tự hỏi có bao nhiêu hồ sơ trên cao đang thêm. Trong Linux, bạn có thể thuyết phục 'thời gian' để thống kê bộ nhớ đầu ra. Điều gì xảy ra nếu bạn biên dịch mà không có hồ sơ và yêu cầu hệ điều hành cho số liệu thống kê bộ nhớ? – MathematicalOrchid
@MathematicalOrchid Tôi đã làm bốn chạy trong tổng số, 2 mà không có hồ sơ (1 threaded/1 không-ren), 2 với hồ sơ. Đầu ra '-sstderr' không thay đổi. Những hình ảnh là từ hai sau chạy. Ngoài ra tôi đã kiểm tra sử dụng mem trong Activity Monitor và tôi không thể nhìn thấy một sự khác biệt lớn giữa w/và w/o profiling. –
OK, đáng để thử. Giờ tôi không còn ý tưởng. : -} – MathematicalOrchid