Ich versuche zu verstehen, wie teuer ein (grün) Gewinde in Haskell (GHC 7.10.1 auf OS X 10.10.5) wirklich ist. Ich bin mir bewusst, dass es im Vergleich zu einem echten OS-Thread sehr billig ist, sowohl für die Speichernutzung als auch für die CPU.Haskell/GHC pro Thread Speicher kostet
rechts, so begann ich mit Gabeln n
(grün) Fäden ein Super einfaches Programm zu schreiben (die ausgezeichnete async
Bibliothek) und dann schläft nur jeden Thread für m
Sekunden.
Nun, das ist einfach genug:
$ 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]
und vor allem wollen wir kompilieren und ausführen:
$ 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
die 100k Fäden Gabel sollte und 10s in jeder warten und uns dann einige drucken Informationen:
$ 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
Es dauerte ziemlich lange (1m33.757s) gegeben, dass jeder Thread nur soll nur für 10s warten, aber wir haben es bauen ohne Gewinde so fair genug für heute. Alles in allem haben wir 350 MB benutzt, das ist nicht schlecht, das sind 3,5 KB pro Thread. Da die anfängliche Stapelgröße (-ki
is 1 KB).
Richtig, aber jetzt wollen wir kompilieren in Gewinde Modus und sehen, ob wir schneller erhalten können:
$ 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, viel schneller, nur 12s jetzt viel besser. Von Activity Monitor habe ich gesehen, dass es ungefähr 4 OS-Threads für die 100k grünen Threads verwendet hat, was sinnvoll ist.
Jedoch 3657 MB Gesamtspeicher! Das ist 10x mehr als die verwendete Version ohne Gewinde ...
Bis jetzt habe ich kein Profiling mit -prof
oder -hy
oder so gemacht. Um zu untersuchen, ein bisschen mehr Ich habe dann einige Haufen Profilierung (-hy
) in separaten läuft. Die Speichernutzung hat sich in beiden Fällen nicht geändert, die Heap-Profiling-Graphen sehen interessanterweise anders aus (links: nicht-threaded, rechts: threaded), aber ich kann den Grund für die 10x-Differenz nicht finden.
Diffing die Profiling-Ausgabe (.prof
Dateien) Ich kann auch keinen wirklichen Unterschied finden.
Deshalb meine Frage: Woher kommt der 10x Unterschied in der Speichernutzung?
EDIT: Nur um es zu erwähnen: Der gleiche Unterschied gilt, wenn das Programm nicht einmal mit Profiling-Unterstützung kompiliert wird. So läuft time ./PerTheadMem 100000 10 +RTS -sstderr
mit ghc -rtsopts -threaded -fforce-recomp PerTheadMem.hs
ist 3559 MB. Und mit ghc -rtsopts -fforce-recomp PerTheadMem.hs
ist es 395 MB.
EDIT 2: Unter Linux (GHC 7.10.2
auf Linux 3.13.0-32-generiC#57-Ubuntu SMP, x86_64
) das gleiche passiert: ohne Gewinde 460 MB in 1m28.538s und Gewinde ist 3483 MB 12.604s ist. /usr/bin/time -v ...
Berichte Maximum resident set size (kbytes): 413684
und Maximum resident set size (kbytes): 1645384
jeweils.
EDIT 3: verändert sich auch das Programm forkIO
direkt zu verwenden:
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]
Und es ändert nichts: ohne Gewinde: 152 MB, mit Gewinde: 3308 MB.
Ich frage mich, wie viel Overhead-Profiling hinzugefügt wird. Unter Linux können Sie 'time' überzeugen, Speicherstatistiken auszugeben. Was passiert, wenn Sie ohne Profiling kompilieren und das Betriebssystem nach Speicherstatistiken fragen? – MathematicalOrchid
@MathematicalOrchid Ich habe vier Läufe insgesamt gemacht, 2 ohne Profilierung (1 Gewinde/1 ohne Gewinde), 2 mit Profilierung. Die '-stderr'-Ausgabe hat sich nicht geändert. Die Bilder stammen von den letzten beiden Läufen. Außerdem habe ich die Verwendung von Mem im Activity Monitor überprüft und konnte keinen großen Unterschied zwischen w/o Profiling sehen. –
OK, einen Versuch wert. Ich habe jetzt keine Ideen mehr. : -} – MathematicalOrchid