仔细思考后,这个问题可以简化为更加精炼的内容。我正在寻找一个Haskell数据结构,该数据结构具有以下特点:
- 看起来像一个列表
- 拥有O(1)查找
- 具有O(1)元素替换或O(1)元素追加(或者是前置...如果那是情况的话,我可以反转我的索引查找)。我可以随时使用其中之一编写我的后续算法。
- 具有非常少的内存开销
我正在尝试构建一个图像文件解析器。文件格式是基本的8位颜色ppm文件,虽然我打算支持16位颜色文件以及PNG和JPEG文件。现有的Netpbm库,尽管有很多未装箱的注释,但在尝试加载我处理的文件时实际上会消耗所有可用内存:
3-10张照片,最小的大小为45MB,最大的大小为110MB。
现在,我无法理解Netpbm代码中的优化,因此我决定自己试试。它是一个简单的文件格式...
我已经开始决定,无论文件格式如何,我都要将最终图像以未压缩的格式存储在其中:
import Data.Vector.Unboxed (Vector)
data PixelMap = RGB8 {
width :: Int
, height :: Int
, redChannel :: Vector Word8
, greenChannel :: Vector Word8
, blueChannel :: Vector Word8
}
然后我编写了一个解析器,它可以像下面这样处理三个向量:
import Data.Attoparsec.ByteString
data Progress = Progress {
addr :: Int
, size :: Int
, redC :: Vector Word8
, greenC :: Vector Word8
, blueC :: Vector Word8
}
parseColorBinary :: Progress -> Parser Progress
parseColorBinary progress@Progress{..}
| addr == size = return progress
| addr < size = do
!redV <- anyWord8
!greenV <- anyWord8
!blueV <- anyWord8
parseColorBinary progress { addr = addr + 1
, redC = redC V.// [(addr, redV)]
, greenC = greenC V.// [(addr, greenV)]
, blueC = blueC V.// [(addr, blueV)] }
在解析器的末尾,我按如下方式构建RGB8:
Progress{..} <- parseColorBinary $ ...
return $ RGB8 width height redC greenC blueC
以这种方式编写的程序,加载其中一个45MB的图像将会消耗5GB或更多的内存。如果我改变
Progress
的定义,使得redC
、greenC
和blueC
都是!(Vector Word8)
,那么程序仍然在合理的内存范围内,但加载单个文件所需的时间太长了,以至于我没有让它完成。最后,如果我用标准列表替换这里的向量,我的内存使用量会飙升到每个文件5GB(我假设...实际上我在达到这个值之前就已经用完了空间),而且加载时间大约为6秒。Ubuntu的预览应用程序一旦启动,几乎瞬间加载并呈现文件。基于这样的理论,即每次调用V.//实际上都会完全复制向量,我尝试切换到
Data.Vector.Unboxed.Mutable
,但是...我甚至无法使其类型检查通过。文档不存在,理解数据类型正在做什么需要与多个其他库进行斗争。而且我甚至不知道它是否能解决问题,所以我非常不愿意尝试。根本问题实际上很简单:如何快速地、不使用过多的内存读取、保留和潜在地操作一个非常大的数据结构?我找到的所有示例都是关于生成临时巨大的数据,然后尽快将其丢弃。
原则上,我希望最终的表示是不可变的,但如果必须使用可变结构才能达到这个目的,我也不太在意。
完整的代码(BSD3许可证)可以在bitbucket上找到:https://bitbucket.org/savannidgerinel/photo-tools。performance分支包含一个严格版本的解析器,可以通过在
Codec.Image.Netpbm
的Progress
数据结构中进行快速更改来使其非严格化。要运行性能测试:
ulimit -Sv 6000000 -- set a ulimit of 6GB, or change to whatever makes sense for you
cabal build
dist/build/perf-test/perf-test +RTS -p -sstderr
repa
或者accelerate
。它们都是为高性能而编写的,因此应该具有许多内存效率优化。 - Danny Navarro