从Socket句柄构建的Lazy ByteString不能被懒散地使用和GCed

时间:2012-05-18 08:59:24

标签: sockets memory haskell lazy-evaluation bytestring

我正在编写网络文件传输应用程序。使用Lazy ByteString作为中间

import qualified Data.ByteString.Lazy as BSL

从本地文件构造BSL时,将BSL放入套接字句柄:

BSL.readFile filename >>= BSL.hPut remoteH  -- OK

这很好用。内存使用量不变。但是要从Socket接收数据,请写入本地文件:

BSL.hGet remoteH size >>= BSL.hPut fileH bs  -- starts swapping in 1 second

我可以看到内存使用率持续上升,BSL占用 size 字节的内存。 更糟糕的是,对于超出我的物理内存大小的 size ,操作系统会立即开始交换。

我必须递归地接收ByteStrings的片段。没关系。

为什么 BSL 表现得那样?

2 个答案:

答案 0 :(得分:4)

hGet是严格的 - 它会立即要求您请求的字节数。它这样做是为了便于数据包级读取。

但是,hGetContentsN是懒惰的,而readFile是以hGetContentsN来实现的。

考虑两个实现:

hGetContentsN :: Int -> Handle -> IO ByteString
hGetContentsN k h = lazyRead -- TODO close on exceptions
  where
    lazyRead = unsafeInterleaveIO loop

    loop = do
        c <- S.hGetSome h k -- only blocks if there is no data available
        if S.null c
          then do hClose h >> return Empty
          else do cs <- lazyRead
                  return (Chunk c cs)

hGet :: Handle -> Int -> IO ByteString
hGet = hGetN defaultChunkSize

hGetN :: Int -> Handle -> Int -> IO ByteString
hGetN k h n | n > 0 = readChunks n
  where
    STRICT1(readChunks)
    readChunks i = do
        c <- S.hGet h (min k i)
        case S.length c of
            0 -> return Empty
            m -> do cs <- readChunks (i - m)
                    return (Chunk c cs)

关键的魔力是hGetContentsN中的懒惰。

答案 1 :(得分:2)

我无法权威地回答延迟字节串的行为,但我建议您研究某种流式方法,例如conduitenumerator。使用管道,您可以编写如下内容:

import Data.Conduit
import Data.Conduit.Binary

main = do
    let filename = "something"
    remoteH <- getRemoteHandle
    runResourceT $ sourceHandle remoteH $$ sinkFile filename

如果您希望使用network-conduit等等,您也可以完全绕过Handle抽象:

runResourceT $ sourceSocket socket $$ sinkFile filename