我试图理解Haskell ( OS 10.10.5上的GHC 7.10.1 )中的(绿色)线程到底有多昂贵。我知道,与真正的OS线程相比,无论是内存使用还是CPU使用,它都非常便宜。
是的,所以我开始用forks 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),因为每个线程应该只等待10s,但我们已经构建了非线程,所以现在足够公平了。总而言之,我们使用了350MB,这还不错,每个线程有3.5KB。给定初始堆栈大小(-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
哇,快多了,现在只有12秒,好多了。在Activity Monitor中,我发现100k绿色线程大致使用了4个OS线程,这是有道理的。
但是,3657MB总内存!这比使用的非线程版本多了10倍...
到目前为止,我还没有使用-prof
或-hy
之类的工具进行性能分析。为了深入研究,我在单独的运行中执行了一些堆分析(-hy
)。内存使用量在这两种情况下都没有变化,堆性能分析图看起来非常不同(左:非线程,右:线程),但我找不到10倍差异的原因。
通过分析分析输出(.prof
文件),我也找不到任何真正的区别。
因此,我的问题是:内存使用量的10倍差异来自哪里?
EDIT:只想提一下:当程序甚至没有使用性能分析支持进行编译时,同样的区别也适用。因此,在ghc -rtsopts -threaded -fforce-recomp PerTheadMem.hs
上运行time ./PerTheadMem 100000 10 +RTS -sstderr
是3559MB。而使用ghc -rtsopts -fforce-recomp PerTheadMem.hs
则是395MB。
编辑2:在Linux (Linux 3.13.0-32-generic #57-Ubuntu SMP, x86_64
上的GHC 7.10.2
)上也会发生同样的情况:非线程化的460MB需要1m28.538秒,而线程化的是3483MB是12.604s。/usr/bin/time -v ...
分别报告了Maximum resident set size (kbytes): 413684
和Maximum resident set size (kbytes): 1645384
。
EDIT 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]
而且它不会改变任何东西:非线程:152MB,线程:3308MB。
https://stackoverflow.com/questions/33149324
复制相似问题