Nel processo di fare un semplice benchmarking, mi sono imbattuto in qualcosa che mi ha sorpreso. Prendete questo frammento da Network.Socket.Splice:Perché hGetBuf, hPutBuf, ecc. Allocano memoria?
hSplice :: Int -> Handle -> Handle -> IO()
hSplice len s t = do
a <- mallocBytes len :: IO (Ptr Word8)
finally
(forever $! do
bytes <- hGetBufSome s a len
if bytes > 0
then hPutBuf t a bytes
else throwRecv0)
(free a)
Ci si aspetterebbe che hGetBufSome
e hPutBuf
qui non avrebbe bisogno di allocare la memoria, come scrivono in e leggere da un buffer di pre-assegnati. Il docs sembrano sostenere questa intuizione up ... Ma, ahimè:
individual inherited
COST CENTRE %time %alloc %time %alloc bytes
hSplice 0.5 0.0 38.1 61.1 3792
hPutBuf 0.4 1.0 19.8 29.9 12800000
hPutBuf' 0.4 0.4 19.4 28.9 4800000
wantWritableHandle 0.1 0.1 19.0 28.5 1600000
wantWritableHandle' 0.0 0.0 18.9 28.4 0
withHandle_' 0.0 0.1 18.9 28.4 1600000
withHandle' 1.0 3.8 18.8 28.3 48800000
do_operation 1.1 3.4 17.8 24.5 44000000
withHandle_'.\ 0.3 1.1 16.7 21.0 14400000
checkWritableHandle 0.1 0.2 16.4 19.9 3200000
hPutBuf'.\ 1.1 3.3 16.3 19.7 42400000
flushWriteBuffer 0.7 1.4 12.1 6.2 17600000
flushByteWriteBuffer 11.3 4.8 11.3 4.8 61600000
bufWrite 1.7 6.9 3.0 9.9 88000000
copyToRawBuffer 0.1 0.2 1.2 2.8 3200000
withRawBuffer 0.3 0.8 1.2 2.6 10400000
copyToRawBuffer.\ 0.9 1.7 0.9 1.7 22400000
debugIO 0.1 0.2 0.1 0.2 3200000
debugIO 0.1 0.2 0.1 0.2 3200016
hGetBufSome 0.0 0.0 17.7 31.2 80
wantReadableHandle_ 0.0 0.0 17.7 31.2 32
wantReadableHandle' 0.0 0.0 17.7 31.2 0
withHandle_' 0.0 0.0 17.7 31.2 32
withHandle' 1.6 2.4 17.7 31.2 30400976
do_operation 0.4 2.4 16.1 28.8 30400880
withHandle_'.\ 0.5 1.1 15.8 26.4 14400288
checkReadableHandle 0.1 0.4 15.3 25.3 4800096
hGetBufSome.\ 8.7 14.8 15.2 24.9 190153648
bufReadNBNonEmpty 2.6 4.4 6.1 8.0 56800000
bufReadNBNonEmpty.buf' 0.0 0.4 0.0 0.4 5600000
bufReadNBNonEmpty.so_far' 0.2 0.1 0.2 0.1 1600000
bufReadNBNonEmpty.remaining 0.2 0.1 0.2 0.1 1600000
copyFromRawBuffer 0.1 0.2 2.9 2.8 3200000
withRawBuffer 1.0 0.8 2.8 2.6 10400000
copyFromRawBuffer.\ 1.8 1.7 1.8 1.7 22400000
bufReadNBNonEmpty.avail 0.2 0.1 0.2 0.1 1600000
flushCharReadBuffer 0.3 2.1 0.3 2.1 26400528
devo assumere questo è apposta ... ma non ho idea di quello che potrebbe essere quello scopo. Ancora peggio: sono appena abbastanza intelligente da ottenere questo profilo, ma non abbastanza intelligente da capire esattamente cosa viene assegnato.
Qualsiasi aiuto lungo quelle linee sarebbe apprezzato.
UPDATE: Ho fatto un po 'di profilatura con due casi di test drasticamente semplificati. Il primo testcase utilizza direttamente i ops di lettura/scrittura da System.Posix.Internals:
echo :: Ptr Word8 -> IO()
echo buf = forever $ do
threadWaitRead $ Fd 0
len <- c_read 0 buf 1
c_write 1 buf (fromIntegral len)
yield
Come ci si spera, questa alloca alcuna memoria sul mucchio ogni volta attraverso il ciclo. Il secondo testcase utilizza i ops di lettura/scrittura da GHC.IO.FD:
echo :: Ptr Word8 -> IO()
echo buf = forever $ do
len <- readRawBufferPtr "read" stdin buf 0 1
writeRawBufferPtr "write" stdout buf 0 (fromIntegral len)
UPDATE # 2: mi è stato consigliato di presentare questo come un bug in GHC Trac ... io non sono ancora sicuro che in realtà è un bug (al contrario di comportamento intenzionale, una limitazione nota, o qualsiasi altra cosa), ma qui è: https://ghc.haskell.org/trac/ghc/ticket/9696
Non stai allocando memoria per creare 'byte'? –
@GabrielGonzalez Io non la penso così ... Quando profilo con '+ RTS -hy', il tipo dominante nell'heap è' ARR_WORDS'. Il tipo di 'byte 'dovrebbe essere' Int' (il numero di byte letti). – mergeconflict
@mergeconflict https://stackoverflow.com/questions/7241470/what-is-arr-words-in-a-ghc-heap-profile#7241686 afferma che ARR_WORDS corrisponde a ByteArray #. Non so perché venga assegnato così tanto, ma il bel plateau nel profilo indica che il programma gira in uno spazio costante. – cheecheeo