我试图了解Haskell(GHC 7.10.1在OS X 10.10.5)中的(绿色)线程是多么昂贵。我知道它与真正的操作系统线程相比非常便宜,无论是内存使用还是CPU。Haskell/GHC每线程内存成本
对,所以我开始写用叉子n
(绿色)线程一个超级简单的程序(使用优秀async
库),然后就睡觉每个线程m
秒。
嗯,这是很容易的:
$ 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]
,并首先,让我们来编译并运行它:
$ 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
应该叉100K线程和等待中的每个10秒,然后打印我们一些信息:
$ 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
花了很长时间(1m33.757s),因为每个线程都应该只是只需等待10秒钟,但我们已经将其构建为非线程,现在已足够公平。总而言之,我们使用了350 MB,这并不算太坏,每个线程3.5 KB。鉴于初始堆栈大小(-ki
is 1 KB)。
权,但现在让我们编译是在线程模式,看看我们是否能够得到任何更快:
$ 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
哇,多更快,只是现在12S,更好的方式。从活动监视器我看到,它大致使用4个OS线程用于100k绿色线程,这很有道理。
但是,3657 MB总内存!这比使用的非线程版本高出10倍...
到目前为止,我没有使用-prof
或-hy
左右进行任何分析。为了调查更多,我然后做了一些堆分析(-hy
)单独运行。在这两种情况下,内存使用都没有变化,堆分析图看起来有趣的不同(左:非线程,右:线程),但我找不到10倍差异的原因。
因此我的问题:内存使用量的10倍差距从哪里来?
编辑:只是提及它:当程序甚至没有编译分析支持时,同样的差异适用。所以与ghc -rtsopts -threaded -fforce-recomp PerTheadMem.hs
运行time ./PerTheadMem 100000 10 +RTS -sstderr
是3559 MB。与ghc -rtsopts -fforce-recomp PerTheadMem.hs
它是395 MB。
编辑2:在Linux(GHC 7.10.2
上Linux 3.13.0-32-generiC#57-Ubuntu SMP, x86_64
)同样的情况:非螺纹在1m28.538s 460 MB和螺纹是3483 MB是12.604s。 /usr/bin/time -v ...
分别报告Maximum resident set size (kbytes): 413684
和Maximum resident set size (kbytes): 1645384
。
编辑3:也改变了程序直接使用forkIO
:
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]
而且它不会改变任何东西:非螺纹:152 MB,螺纹:3308 MB。
我想知道多少开销分析正在添加。在Linux下,你可以说服'时间'输出内存统计信息。如果编译时没有分析并询问操作系统内存统计信息会发生什么? – MathematicalOrchid
@MathematicalOrchid我总共做了4次运行,2次没有分析(1个线程/ 1个非线程),2个分析。 '-sstderr'输出没有改变。图片来自后两次运行。另外我在Activity Monitor中检查了mem的使用情况,在w /和w/o分析之间我看不到差异。 –
好的,值得一试。我现在没有想法。 : - } – MathematicalOrchid