【问题标题】:Lazy ByteString built from Socket handle cannot be consumed and GCed lazily从 Socket 句柄构建的 Lazy ByteString 不能被延迟消费和 GC
【发布时间】:2012-05-18 08:59:23
【问题描述】:

我正在编写一个网络文件传输应用程序。使用 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 字节的内存。 更糟糕的是,对于超出我的物理内存大小的大 size,操作系统会立即开始交换。

我必须递归地接收字节串的片段。没关系。

为什么 BSL 会这样?

【问题讨论】:

    标签: sockets memory haskell lazy-evaluation bytestring


    【解决方案1】:

    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 的懒惰。

    【讨论】:

      【解决方案2】:

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

      import Data.Conduit
      import Data.Conduit.Binary
      
      main = do
          let filename = "something"
          remoteH <- getRemoteHandle
          runResourceT $ sourceHandle remoteH $$ sinkFile filename
      

      如果您愿意,也可以完全绕过 Handle 抽象,使用 network-conduit 和类似的东西:

      runResourceT $ sourceSocket socket $$ sinkFile filename
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-02
        • 1970-01-01
        • 1970-01-01
        • 2018-06-08
        • 1970-01-01
        • 2023-02-09
        相关资源
        最近更新 更多