在Haskell中解析二进制文件时如何减少垃圾收集?

时间:2013-12-25 02:48:50

标签: haskell optimization

我正在编写一个使用Haskell解析.TGA文件的程序 - 但是,性能绝对可怕。一个2048 x 2048像素的图像需要几秒钟才能解析。

我使用+RTS -p -RTS运行了我的代码,并从报告中收到了以下有趣的内容:

total time = 1.08 secs
total alloc = 3,037,568,120 bytes

COST CENTRE           MODULE    %time    %alloc
readPixelMap            Main     33.0      11.0
readPixelMap.getByte    Main     32.7      75.1
readPixelMap.getColor   Main     27.0      13.3

我的程序似乎正在readPixelMap函数中分配巨大的内存量。该功能如下所示:

readPixelMap width height = do
    pixels <- replicateM height (replicateM width getColor)
    return $ PixMap pixels
    where getByte = liftM toInteger getWord8
          getColor = do (red:green:blue:alpha:xs) <- replicateM 4 getByte
                        return (red, green, blue, alpha)

PixMap定义为

data PixMap = PixMap [[RGBAColor]] deriving (Show)
type RGBAColor = (Integer, Integer, Integer, Integer)
使用readPixelMap中的Get monad调用

Data.Binary

main = do
    binary <- BS.readFile "test.tga"
    let (pixelMap, binary', nil) = runGetState (readPixelMap 2048 2048) binary 0

我一直在努力工作的印象是2048 x 2048 .TGA图像应加载到几百兆字节(最多)的内存中。有没有明显的方法来改善我的垃圾收集时间/分配金额?

2 个答案:

答案 0 :(得分:12)

您的RGBColor类型使用5个机器字,其中4个是指向每个Integer的指针。每个Integer包含一个用于GC /标记的机器字,以及用于小型表示的一个附加字,或者包含指向GMP数据的字节数组和肢体计数的指针的大型表示。

此外,您还使用列表列表来存储值。每个非空列表节点都是一个用于GC /标记的单词,一个指向该值的单词指针,以及一个指向尾部的单词指针。

要使用更少的内存,请使用适当的数据类型。尽可能使用未打包的值。对于8位值,请使用Word8代替Integer。使用数组而不是列表。你知道,像你一样关心记忆的基本知识。

查看http://johantibell.com/files/slides.pdf的一些基础知识。

答案 1 :(得分:2)

  1. 使用Integer元组来存储颜色数据(总是32位数据)是没有意义的。为什么不使用以下任何变体:(Word8, Word8, Word8, Word8); data Colour = C Word8 Word8 Word8 Word; data Colour = C Word32

  2. 懒惰的ByteString只是严格ByteString的列表。因此,如果您逐字节地读取数据,您将基本上生成一个严格ByteString的列表,其长度为2048 * 2048。效率不高。

  3. 使用[[a]]存储二维数组也是如此。就内存性能而言,这只是您可以使用的最差数据类型。尝试以下任何一个:Array (Int, Int) Color; UArray (Int, Int) Color; (Int, Int, Ptr Word8)。我的偏好是来自Data.Array.Repa的数组,它们具有高性能,具有许多内置遍历,折叠等,并且自然支持并行化。另一个很大的优势是Data.Array.Repa.Repr.ByteString.fromByteString会将严格的ByteString转换为数组以进行进一步操作。关于这一点最好的部分是你在阅读阵列时不再需要担心性能;它已经为你完成了!

  4. 一个例子:

    import Data.Array.Repa.Repr.ByteString
    import Data.Array.Repa
    import qualified Data.ByteString as B
    import Data.Word (Word8)
    
    data TGA = TGA (Array B DIM2 Word8)
    
    readTGAFromFile :: FilePath -> (Int, Int) -> IO TGA
    readTGAFromFile file (width,height) = do
      dat <- B.readFile file
      return $ TGA $ fromByteString (Z :. height :. width) dat