Каковы основные различия между API-интерфейсами Repa 2 и 3?
Чтобы быть более конкретным, у меня есть следующая безобидная маленькая программа Repa 3:
{-# LANGUAGE QuasiQuotes #-}
import Prelude hiding (map, zipWith)
import System.Environment (getArgs)
import Data.Word (Word8)
import Data.Array.Repa
import Data.Array.Repa.IO.DevIL
import Data.Array.Repa.Stencil
import Data.Array.Repa.Stencil.Dim2
main = do
[s] <- getArgs
img <- runIL $ readImage s
let out = output x where RGB x = img
runIL . writeImage "out.bmp" . Grey =<< computeP out
output img = map cast . blur . blur $ blur grey
where
grey = traverse img to2D luminance
cast n = floor n :: Word8
to2D (Z:.i:.j:._) = Z:.i:.j
---------------------------------------------------------------
luminance f (Z:.i:.j) = 0.21*r + 0.71*g + 0.07*b :: Float
where
(r,g,b) = rgb (fromIntegral . f) i j
blur = map (/ 9) . convolve kernel
where
kernel = [stencil2| 1 1 1
1 1 1
1 1 1 |]
convolve = mapStencil2 BoundClamp
rgb f i j = (r,g,b)
where
r = f $ Z:.i:.j:.0
g = f $ Z:.i:.j:.1
b = f $ Z:.i:.j:.2
Это занимает много времени, чтобы обработать изображение 640x420 на моем двухъядерном ноутбуке 2Ghz:
real 2m32.572s
user 4m57.324s
sys 0m1.870s
Я знаю, что что-то должно быть совершенно неправильным, потому что я получил гораздо лучшую производительность по гораздо более сложным алгоритмам с использованием Repa 2. В рамках этого API большое улучшение, которое я обнаружил, произошло от добавления вызова "force" перед каждым преобразованием массива ( который я понимаю, чтобы иметь в виду каждый вызов для отображения, свертки, прохождения и т.д.). Я не совсем понимаю, что делать в Repa 3 - на самом деле я думал, что новые параметры типа манифестации предполагают, что нет никакой двусмысленности в том, когда нужно принудительно заставить массив? И как новый монадический интерфейс вписывается в эту схему? Я прочитал хороший учебник от Don S, но есть некоторые ключевые пробелы между API-интерфейсами Repa 2 и 3, которые мало обсуждаются онлайн AFAIK.
Проще говоря, существует ли минимально эффективный способ исправления вышеуказанной эффективности программы?
Ответы
Ответ 1
Параметры нового типа представления не при необходимости автоматически принудительно принудительно (это, вероятно, сложная проблема), вам все равно придется вручную. В Repa 3 это выполняется с помощью функции computeP:
computeP
:: (Monad m, Repr r2 e, Fill r1 r2 sh e)
=> Array r1 sh e -> m (Array r2 sh e)
Я лично не понимаю, почему это монадично, потому что вы можете использовать Monad Identity:
import Control.Monad.Identity (runIdentity)
force
:: (Repr r2 e, Fill r1 r2 sh e)
=> Array r1 sh e -> Array r2 sh e
force = runIdentity . computeP
Итак, теперь ваша функция output
может быть переписана с соответствующим форсированием:
output img = map cast . f . blur . f . blur . f . blur . f $ grey
where ...
с аббревиатурой f
с помощью вспомогательной функции u
, чтобы помочь вывести тип:
u :: Array U sh e -> Array U sh e
u = id
f = u . force
С этими изменениями ускорение довольно драматично, чего нельзя ожидать, так как без промежуточного форсирования каждый выходной пиксель заканчивается, оценивая гораздо больше, чем это необходимо (промежуточные значения не разделяются).
Исходный код:
real 0m25.339s
user 1m35.354s
sys 0m1.760s
С форсированием:
real 0m0.130s
user 0m0.320s
sys 0m0.028s
Протестировано с размером 600x400 png, выходные файлы были идентичны.
Ответ 2
computeP
- это новый force
.
В Repa 3 вам нужно использовать computeP
везде, где вы бы использовали force
в Repa 2.
Пример Laplace из примеров repa аналогичен тому, что вы делаете. Вы также должны использовать cmap
вместо обычного map
в вашей функции blur
. Там будет документ, объясняющий, почему на моей домашней странице в начале следующей недели.