Каковы основные различия между 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. Там будет документ, объясняющий, почему на моей домашней странице в начале следующей недели.