3

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

import qualified Data.ByteString.Lazy as BSL

从本地文件构造 BSL 时,将 BSL 放入 Socket 的句柄中:

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

这工作正常。内存使用是恒定的。但要从 Socket 接收数据,则写入本地文件:

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

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

我必须递归地接收 ByteStrings 段。那没问题。

为什么BSL会这样?

4

2 回答 2

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

于 2012-05-18T12:02:26.507 回答
2

我无法权威地回答惰性字节串的行为,但我建议您研究某种流式处理方法,例如管道枚举器。使用导管,您可以编写如下内容:

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
于 2012-05-18T10:06:58.777 回答