11

Per essere più precisi, ho il seguente piccolo programma innocua Repa 3:Quali sono le differenze chiave tra le API Repa 2 e 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 

che prende così tanto tempo per elaborare un'immagine di 640x420 sul mio nucleo 2Ghz 2 Duo per notebook:

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

So che qualcosa deve essere sbagliato, perché ho ottenuto prestazioni molto migliori su algoritmi molto più complessi usando Repa 2. Sotto quell'API, il grande miglioramento che ho trovato è venuto dall'aggiungere una chiamata a "force" prima di ogni array transform (che ho capire per significare ogni chiamata per mappare, convogliare, attraversare ecc.). Non riesco a distinguere la cosa analoga da fare in Repa 3 - in effetti ho pensato che i nuovi parametri del tipo manifest dovrebbero garantire che non ci sia ambiguità su quando un array deve essere forzato? E come si inserisce la nuova interfaccia monadica in questo schema? Ho letto il bel tutorial di Don S, ma ci sono alcune lacune chiave tra le API di Repa 2 e 3 che sono poco discusse in AFAIK online.

Più semplicemente, c'è un modo minimo di impatto per correggere l'efficienza del programma di cui sopra?

risposta

10

I nuovi parametri del tipo di rappresentazione non vengono forzati automaticamente quando necessario (è probabilmente un problema difficile farlo bene): è necessario forzare manualmente. In Repa 3 questo è fatto con la funzione computeP:

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

Io personalmente davvero non capisco perché è monadica, perché si può benissimo usare Monade Identità:

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 

Così, ora il tuo output funzione può essere riscritto con un'appropriata forzatura:

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

con un'abbreviazione f utilizzando una funzione di supporto per aiutare u inferenza:

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

Con queste modifiche, l'aumento di velocità è abbastanza drammatica - che deve essere previsto, come colpo intermedio forzare ciascun pixel risultante finisce valutazione molto più del necessario (i valori intermedi non sono condivisi) .

tuo codice originale:

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

Con forzatura:

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

Testato con un 600x400 png, i file di output erano identici.

+0

Questa è un'ottima risposta! Avevo capito che computeP è il sostituto di "force", ma non avevo pensato di usarlo con la monade dell'identità. Apprezzo il vostro aiuto. – sacheie

+1

Credo che la ragione per cui si usano i tipi di ritorno monadici sia perché l'idea di forzare qualcosa è strettamente legata alle forzature che avvengono in sequenza. C'è una spiegazione migliore in http://www.cse.unsw.edu.au/~chak/papers/LCKP12.html – Axman6

7

computeP è il nuovo force.

In Repa 3 è necessario utilizzare computeP ovunque si sarebbe usato force in Repa 2.

Il Laplace esempio da Repa-esempi è simile a quello che stai facendo. È inoltre necessario utilizzare cmap anziché il normale map nella funzione blur. Ci sarà un articolo che spiegherà perché sulla mia homepage all'inizio della prossima settimana.

+0

Il bello della comunità Haskell - feedback degli stessi sviluppatori della biblioteca :) Attendo con impazienza il tuo articolo. – sacheie

Problemi correlati