Быстрее SumSquareDifference в Haskell

Я реализую алгоритм сжатия фрактальных изображений двоичных изображений в Haskell. Для этой цели мне нужно найти для заданного блока диапазона (под-изображения) ближайшее изображение в так называемом пуле доменов, списке списков изображений. Я сравниваю изображения, вычисляя суммарную квадратную разницу значений обоих их пикселей.

Я использую библиотеку Haskell Image Processing (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

Производительность этого кода действительно плохая. Сжатие изображения 256x256 с размером блока 2 занимает около 5 минут, несмотря на компиляцию с -O2. Профилирование показывает мне, что большая часть времени выполнения тратится на функцию distance, особенно в sumSquareDifference, но и в toLists и toBinList:

       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:

Мне удалось значительно увеличить производительность, создав dPool с функцией genDistanceList, который останавливает вычисление, как только будут найдены два одинаковых изображения:

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

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

Здесь fold и zipWith предоставлены hipне base.

Другие вопросы по тегам