【问题标题】:How can I convert a (StorableArray (Int, Int) Word8) into a lazy ByteString?如何将 (StorableArray (Int, Int) Word8) 转换为惰性 ByteString?
【发布时间】:2010-06-27 16:48:22
【问题描述】:

我正在尝试加载 PNG 文件,获取未压缩的 RGBA 字节,然后将它们发送到 gzip 或 zlib 包。

pngload 包以 (StorableArray (Int, Int) Word8) 形式返回图像数据,压缩包采用惰性字节字符串。因此,我正在尝试构建一个 (StorableArray (Int, Int) Word8 -> ByteString) 函数。

到目前为止,我已经尝试了以下方法:

import qualified Codec.Image.PNG as PNG
import Control.Monad (mapM)
import Data.Array.Storable (withStorableArray)
import qualified Data.ByteString.Lazy as LB (ByteString, pack, take)
import Data.Word (Word8)
import Foreign (Ptr, peekByteOff)

main = do
    -- Load PNG into "image"...
    bytes <- withStorableArray 
        (PNG.imageData image)
        (bytesFromPointer lengthOfImageData)

bytesFromPointer :: Int -> Ptr Word8 -> IO LB.ByteString
bytesFromPointer count pointer = LB.pack $ 
    mapM (peekByteOff pointer) [0..(count-1)]

这会导致堆栈内存不足,所以很明显我做错了什么。我可以用 Ptr 和 ForeignPtr 尝试更多的东西,但是那里有很多“不安全”的功能。

如有任何帮助,我们将不胜感激;我很困惑。

【问题讨论】:

    标签: arrays haskell bytestring


    【解决方案1】:

    通常,打包和解包对于性能来说是个坏主意。如果你有一个 Ptr 和一个以字节为单位的长度,你可以通过两种不同的方式生成一个严格的字节串:

    像这样:

    import qualified Codec.Image.PNG as PNG
    import Control.Monad
    import Data.Array.Storable (withStorableArray)
    
    import Codec.Compression.GZip
    
    import qualified Data.ByteString.Lazy   as L
    import qualified Data.ByteString.Unsafe as S
    
    import Data.Word
    import Foreign
    
    -- Pack a Ptr Word8 as a strict bytestring, then box it to a lazy one, very
    -- efficiently
    bytesFromPointer :: Int -> Ptr Word8 -> IO L.ByteString
    bytesFromPointer n ptr = do
        s <- S.unsafePackCStringLen (castPtr ptr, n)
        return $! L.fromChunks [s]
    
    -- Dummies, since they were not provided 
    image = undefined
    lengthOfImageData = 10^3
    
    -- Load a PNG, and compress it, writing it back to disk
    main = do
        bytes <- withStorableArray
            (PNG.imageData image)
            (bytesFromPointer lengthOfImageData)
        L.writeFile "foo" . compress $ bytes
    

    我使用的是 O(1) 版本,它只是从 StorableArray 中重新打包 Ptr。您可能希望先通过“packCStringLen”复制它。

    【讨论】:

      【解决方案2】:

      您的“bytesFromPointer”的问题是您从 pngload 中获取了一个打包表示形式,即 StorableArray,并且您希望通过中间列表将其转换为另一个打包表示形式,一个 ByteString。有时懒惰意味着中间列表不会在内存中构建,但这里不是这样。

      函数“mapM”是第一个违规者。如果你扩展mapM (peekByteOff pointer) [0..(count-1)],你会得到

      el0 <- peekByteOff pointer 0
      el1 <- peekByteOff pointer 1
      el2 <- peekByteOff pointer 2
      ...
      eln <- peekByteOff pointer (count-1)
      return [el0,el1,el2,...eln]
      

      因为这些动作都发生在 IO monad 中,所以它们是按顺序执行的。这意味着必须在构造列表之前构造输出列表的每个元素,而懒惰永远无法帮助您。

      即使列表是懒惰的,正如 Don Stewart 指出的那样,“pack”功能仍然会破坏你的表现。 “pack”的问题在于它需要知道列表中有多少元素才能分配正确的内存量。为了找到一个列表的长度,程序需要遍历它到最后。由于需要计算长度,列表需要完全加载后才能打包成字节串。

      我认为“mapM”和“pack”是一种代码味道。有时您可以将“mapM”替换为“mapM_”,但在这种情况下,最好使用字节串创建函数,例如"packCStringLen"。

      【讨论】:

        猜你喜欢
        • 2018-11-22
        • 1970-01-01
        • 2014-05-22
        • 1970-01-01
        • 1970-01-01
        • 2020-07-17
        • 2013-04-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多