Приведенный ниже код является всего лишь экспериментом, чтобы увидеть, что происходит, когда вызывается forkIO
(чтобы проверить издержки облегченного потока GHC - и накладные расходы памяти, и эффект конкуренции для MVar) с функцией, которая просто читает и записывает MVar
. Я знаю, что будет конфликт доступа, когда 1 миллион потоков ожидает доступа к MVar - но поскольку каждая функция обращается к нему только один раз, все они должны в конечном итоге пройти. В моих тестах я вижу, что если я компилирую код с опцией -threaded
(GHC 7.0.3, Mac, x86_64), код сильно замедляется - в ~ 110 раз. Я буду признателен за указатели на причину замедления в коде с опцией -threaded
.
{-# LANGUAGE BangPatterns #-}
import Control.Concurrent (forkIO, MVar, newEmptyMVar, putMVar, takeMVar)
import qualified Data.Vector.Storable.Mutable as MSV
import Control.Monad.Primitive (PrimState)
import Control.Monad (mapM_)
f :: MVar Int -> IO ()
f m = do
!i <- takeMVar m
putMVar m (i+1)
main = do
m <- newEmptyMVar
putMVar m 0
let l = 1000000
mapM_ (\x -> forkIO $ f m) [1..l]
При компиляции с ghc -O2 -rtsopts
:
./test +RTS -s
1,070,652,216 bytes allocated in the heap
1,023,908,744 bytes copied during GC
1,872 bytes maximum residency (1 sample(s))
177,328 bytes maximum slop
9 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 2029 collections, 0 parallel, 0.13s, 0.13s elapsed
Generation 1: 1 collections, 0 parallel, 0.00s, 0.00s elapsed
INIT time 0.00s ( 0.00s elapsed)
MUT time 0.08s ( 0.08s elapsed)
GC time 0.13s ( 0.13s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 0.21s ( 0.22s elapsed)
%GC time 61.9% (61.7% elapsed)
Alloc rate 13,110,134,156 bytes per MUT second
Productivity 37.9% of total user, 37.2% of total elapsed
При компиляции с ghc -O2 -rtsopts -threaded
(я использовал -N3
для четырехъядерного IMAC):
./test +RTS -s -N3
1,096,608,080 bytes allocated in the heap
2,713,129,232 bytes copied during GC
761,160,288 bytes maximum residency (10 sample(s))
711,798,176 bytes maximum slop
2424 MB total memory in use (0 MB lost due to fragmentation)
Generation 0: 1177 collections, 1176 parallel, 46.51s, 15.93s elapsed
Generation 1: 10 collections, 10 parallel, 9.35s, 5.60s elapsed
Parallel GC work balance: 1.05 (339027672 / 323162843, ideal 3)
MUT time (elapsed) GC time (elapsed)
Task 0 (worker) : 0.00s ( 0.00s) 0.00s ( 0.00s)
Task 1 (worker) : 56.95s ( 1.49s) 0.26s ( 0.09s)
Task 2 (worker) : 57.05s ( 1.49s) 0.16s ( 0.05s)
Task 3 (bound) : 1.49s ( 1.23s) 55.27s ( 21.33s)
Task 4 (worker) : 57.20s ( 1.49s) 0.00s ( 0.00s)
Task 5 (worker) : 57.03s ( 1.49s) 0.18s ( 0.06s)
SPARKS: 0 (0 converted, 0 pruned)
INIT time 0.00s ( 0.00s elapsed)
MUT time 0.90s ( 1.23s elapsed)
GC time 55.86s ( 21.53s elapsed)
EXIT time 0.45s ( 0.40s elapsed)
Total time 57.21s ( 23.02s elapsed)
%GC time 97.6% (93.5% elapsed)
Alloc rate 811,808,581 bytes per MUT second
Productivity 2.4% of total user, 5.9% of total elapsed
gc_alloc_block_sync: 19789
whitehole_spin: 73
gen[0].sync_large_objects: 0
gen[1].sync_large_objects: 0
Как вы можете видеть выше, общий объем используемой памяти увеличивается с ~ 9 МБ до ~ 2 ГБ в режиме -threaded
. Общий объем памяти, выделяемой в куче, в обоих случаях находится в пределах ~ 1%. Я подозреваю, что большая часть резидентной памяти возникает из-за разветвления функции, поскольку каждый экземпляр функции в куче должен быть thunk. Так как я не использовал ни одну из стратегий номинала, искр нет.
Я написал этот код, потому что мне интересно поведение с режимом -threaded
и без него, а не потому, что я планирую использовать его таким образом. По сути, я учу себя, что произойдет, если ты напишешь такой плохой код. Таким образом, вы не должны говорить, что не пишите так:)
РЕДАКТИРОВАТЬ 1:
ehird указал в комментарии, чтобы проверить, что threaded
сама среда выполнения не способствует замедлению. Похоже, что многопотоковое выполнение не способствует замедлению - компиляция с опцией -threaded
не влияет на производительность. Замедление происходит только при использовании опции +RTS -N
во время выполнения.