11

Para ser más específico, tengo el siguiente programa poco de aspecto inocuo Repa 3:¿Cuáles son las principales diferencias entre las API Repa 2 y 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 

que toma tanto tiempo para procesar una imagen de 640x420 en mi 2 GHz Core 2 Duo portátil:

real 2m32.572s 
user 4m57.324s 
sys  0m1.870s 

Sé que algo debe estar bastante mal, porque he obtenido un rendimiento mucho mejor en algoritmos mucho más complejos usando Repa 2. Bajo esa API, la gran mejora que encontré vino de agregar una llamada a 'fuerza' antes de cada transformación de matriz (que entender que significa cada llamada para mapear, convulsionar, atravesar, etc.). No puedo entender lo análogo a hacer en Repa 3, de hecho, pensé que los nuevos parámetros de tipo de manifestación deberían garantizar que no haya ambigüedad sobre cuándo se debe forzar una matriz. ¿Y cómo encaja la nueva interfaz monádica en este esquema? He leído el buen tutorial de Don S, pero hay algunas brechas clave entre las API Repa 2 y 3 que se discuten poco en línea AFAIK.

Más simplemente, ¿hay una forma mínimamente impactante de corregir la eficacia del programa anterior?

Respuesta

10

Los nuevos parámetros del tipo de representación no fuerzan automágicamente cuando es necesario (probablemente sea un problema difícil hacerlo bien) - aún debe forzar manualmente. En Repa 3 esto se hace con la función computeP:

computeP 
    :: (Monad m, Repr r2 e, Fill r1 r2 sh e) 
    => Array r1 sh e -> m (Array r2 sh e) 

personalmente realmente no entiendo por qué es monádica, porque se puede igual de bien utilizar Mónada Identidad:

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 

Por lo tanto, ahora su output función puede ser reescrita con forzar apropiado:

output img = map cast . f . blur . f . blur . f . blur . f $ grey 
    where ... 

con una abreviatura f utilizando una función de ayuda para ayudar u inferencia de tipos:

u :: Array U sh e -> Array U sh e 
u = id 
f = u . force 

Con estos cambios, el aumento de velocidad es bastante dramático - que es de esperar, ya que sin intermedio forzando cada píxel de salida termina la evaluación mucho más de lo necesario (los valores intermedios no se comparten) .

Su código original:

real 0m25.339s 
user 1m35.354s 
sys  0m1.760s 

Con forzando:

real 0m0.130s 
user 0m0.320s 
sys  0m0.028s 

Probado con un png 600x400, los archivos de salida eran idénticos.

+0

¡Esta es una gran respuesta! Entendí que computeP es el reemplazo de 'force', pero no había pensado usarlo con la mónada de identidad. Aprecio tu ayuda. – sacheie

+1

Creo que la razón para usar tipos de retorno monádico es porque la idea de forzar algo está muy ligada a las fuerzas que ocurren secuencialmente. Hay una mejor explicación en http://www.cse.unsw.edu.au/~chak/papers/LCKP12.html – Axman6

7

computeP es el nuevo force.

En Repa 3 es necesario utilizar todas partes computeP que habría utilizado force en Repa 2.

El Laplace ejemplo de repa-ejemplos es similar a lo que está haciendo. También debe usar cmap en lugar de map en su función blur. Habrá un documento explicando por qué en mi página de inicio la próxima semana.

+0

Lo bueno de la comunidad Haskell: comentarios de los propios desarrolladores de la biblioteca :) Espero con impaciencia su publicación. – sacheie

Cuestiones relacionadas