【发布时间】:2014-12-07 15:41:04
【问题描述】:
在做一些简单的基准测试的过程中,我遇到了一些令我惊讶的事情。从Network.Socket.Splice 获取这个sn-p:
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)
人们会期望这里的hGetBufSome 和hPutBuf 不需要分配内存,因为它们写入和读取预先分配的缓冲区。 docs 似乎支持这种直觉......但是唉:
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
我不得不假设这是故意的……但我不知道那个目的可能是什么。更糟糕的是:我只是勉强能得到这个配置文件,但还不够聪明,无法准确地弄清楚分配了什么。
我们将不胜感激。
更新:我用两个大大简化的测试用例做了更多的分析。第一个测试用例直接使用来自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
正如您所希望的,每次循环都不会在堆上分配内存。第二个测试用例使用来自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)
更新 #2: 有人建议我将此作为 GHC Trac 中的错误提交...我仍然不确定它实际上 是否是一个错误(相反故意行为、已知限制或其他)但这里是:https://ghc.haskell.org/trac/ghc/ticket/9696
【问题讨论】:
-
你不是分配内存来创建
bytes吗? -
@GabrielGonzalez 我不这么认为...当我使用
+RTS -hy进行概要分析时,堆上的主要类型是ARR_WORDS。bytes的类型应该是Int(读取的字节数)。 -
@mergeconflict stackoverflow.com/questions/7241470/… 声明 ARR_WORDS 对应于 ByteArray#。我不知道为什么要分配这么多,但配置文件中漂亮的平台表明程序在恒定空间中运行。
-
@cheecheeo 是的,没错。我没有空间泄漏;相反,似乎每次循环都在分配和释放一个或多个小缓冲区。
-
您是否分析过执行其他操作(可能是无操作)的代码并验证您获得了不同的行为?