【问题标题】:Faster SumSquareDifference in HaskellHaskell中更快的SumSquareDifference
【发布时间】:2020-10-07 22:46:16
【问题描述】:

我正在 Haskell 中实现二进制图像的分形图像压缩算法。为此,我必须在所谓的域池中找到给定范围块(子图像)最接近的图像,即图像列表列表。我通过计算两个像素值的平方和差来比较图像。

我使用 Haskell 图像处理 (HIP) 库来读取和写入图像。

compress :: Image VS X Bit -> Int -> [(Int, Int)]
compress img blockSize = zip dIndices tIndices
    where rImg = img
          dImg = downsample2 rImg
          rBlocks = (toBlocks rImg blockSize) :: [Image VS X Bit]
          dBlocks = (toBlocks dImg blockSize) :: [Image VS X Bit]
          dPool = (createDPool dBlocks) :: [[Image VS X Bit]]
          distanceLists = map (\x -> (map.map) (distance x) dPool) rBlocks
          dIndices = map (fst . getMinIndices) distanceLists
          tIndices = map (snd . getMinIndices) distanceLists


distance :: Image VS X Bit -> Image VS X Bit-> Int
distance x y = sumSquareDifference (toBinList x) (toBinList y)
    where toBinList = map (toNum . extractBitOfPixel) . concat . toLists

toLists :: MArray arr cs e => Image arr cs e -> [[Pixel cs e]]
toLists img = [[index img (i, j) | j <- [0..cols img -1]] | i <- [0.. rows img -1]]

extractBitOfPixel :: Pixel X Bit -> Bit
extractBitOfPixel (PixelX b) = b

sumSquareDifference :: [Int] -> [Int] -> Int
sumSquareDifference a b = sum $ zipWith (\x y -> (x-y)^2) a b

这段代码的性能真的很差。尽管使用-O2 进行编译,但压缩块大小为 2 的 256x256 图像大约需要 5 分钟。 Profiling 显示大部分运行时都花在函数distance 中,尤其是在 sumSquareDifference 中,而且在toListstoBinList 中:

       binaryCompressionSimple +RTS -p -RTS

    total time  =     1430.89 secs   (1430893 ticks @ 1000 us, 1 processor)
    total alloc = 609,573,757,744 bytes  (excludes profiling overheads)

COST CENTRE               MODULE    SRC                                        %time %alloc

sumSquareDifference       Main      binaryCompressionSimple.hs:87:1-63          30.9   28.3
toLists                   Main      binaryCompressionSimple.hs:66:1-90          20.3   47.0
distance.toBinList        Main      binaryCompressionSimple.hs:74:11-79         10.9   15.1
main                      Main      binaryCompressionSimple.hs:(14,1)-(24,21)    7.3    0.0
compress                  Main      binaryCompressionSimple.hs:(28,1)-(36,60)    6.9    0.0
distance                  Main      binaryCompressionSimple.hs:(71,1)-(74,79)    5.7    0.9
compress.distanceLists.\  Main      binaryCompressionSimple.hs:34:38-65          5.2    4.4
compress.distanceLists    Main      binaryCompressionSimple.hs:34:11-74          2.8    0.0
main.\                    Main      binaryCompressionSimple.hs:20:72-128         2.7    0.0
getMinIndices.getMinIndex Main      binaryCompressionSimple.hs:116:11-53         2.7    1.8
sumSquareDifference.\     Main      binaryCompressionSimple.hs:87:52-58          2.7    2.5

有没有办法提高性能?

块大小为 2 表示将 16384 个范围块与域池的 131072 个图像进行比较,因此 sumSquareDifference 将被调用 (16384*131072=)2147483648 次,每次计算两个长度为 4 的列表的平方和差.我意识到这是一个很大的数字,但代码不应该更快(懒惰评估列表)吗?这是 Haskell 问题还是算法问题?

编辑:

通过使用,我至少能够将性能提高三分之一:

distance :: Image VS X Bit -> Image VS X Bit-> Int
distance x y
     | x == y = 0
     | otherwise = sumSquareDifference (toBinList x) (toBinList y)
    where toBinList = map (toNum . extractBitOfPixel) . concat . inlinedToLists

编辑 2:

通过使用函数genDistanceList 创建dPool,我能够极大地提高性能,一旦找到两个相同的图像就会停止计算:

genDistanceList :: [[Image VS X Bit]] -> Image VS X Bit -> [[Int]]
genDistanceList dPool rBlock = nestedTakeWhileInclusive (/= 0) $ (map.map) (distance rBlock) dPool

【问题讨论】:

  • “这是 Haskell 问题还是算法问题?”大概两者兼而有之。我觉得这个问题有点笼统,你能不能再集中一点?
  • @leftaroundabout 我的主要问题是,提高性能的可能性是什么?我可能无法更改算法的任何内容。
  • re:“代码不应该更快吗(懒惰评估列表)?”当你的结果是一个总和时,懒惰的评估不能做太多 - 在你访问所有被求和的值之前,你无法知道总和的值,所以懒惰不会忽略任何计算。
  • ...但我怀疑您可以在这里制定更好的算法。由于您的像素是位,因此您的“sumSquareDifference”实际上只是计算有多少位不同。它应该比转换为Int、减法和平方作为第一遍更有效;一旦计数超过当前最小值,跳过图像的其余部分是另一个明显的优化第一步。
  • 不,Haskell 不会神奇地记住所有之前计算的函数调用。一般来说,这将使用大量内存并且效率非常低。如果您有大量重复项,也许您应该避免大量重复项?但我想重复在灰度中不太常见。

标签: haskell fractals hip


【解决方案1】:

首先要尝试的是跳过到列表的转换:

{-# INLINE numIndex #-}
numIndex :: Image VS X Bit -> (Int, Int) -> Int
numIndex img pos = toNum . extractBitOfPixel $ index img pos

distance :: Image VS X Bit -> Image VS X Bit -> Int
distance a b = sum
    [ (numIndex a pos - numIndex b pos)^2
    | i <- [0 .. cols a-1]
    , j <- [0 .. rows a-1]
    , let pos = (i, j)
    ]

由于您没有向我们提供可重现的最小示例,因此无法判断这会产生什么影响(如果有的话)。如果您需要更好的建议,请提供更好的数据。

编辑

通过黑线鳕寻找臀部,我怀疑以下内容会更好:

distance :: Image VS X Bit -> Image VS X Bit -> Int
distance a b = id
    . getX
    . fold (+)
    $ zipWith bitDistance a b

bitDistance :: Pixel X Bit -> Pixel X Bit -> Pixel X Int
bitDistance (PixelX a) (PixelX b) = PixelX (fromIntegral (a-b))
-- use (a-b)^2 when you switch to grayscale, but for Bit the squaring isn't needed

这里foldzipWithhip提供的,不是base提供的。

【讨论】:

  • 非常感谢您的帮助!感谢您的建议,我的性能又提高了 14%。
猜你喜欢
  • 2015-02-17
  • 2010-11-11
  • 1970-01-01
  • 2011-05-15
  • 2011-08-02
  • 1970-01-01
  • 1970-01-01
  • 2011-12-15
  • 1970-01-01
相关资源
最近更新 更多