2011-07-20 9 views
7

Ho sentito parlare molto delle prestazioni sorprendenti dei programmi scritti in Haskell e volevo fare alcuni test. Quindi, ho scritto una "libreria" per le operazioni con le matrici solo per confrontare le sue prestazioni con le stesse cose scritte in puro C. Prima di tutto ho testato le prestazioni di moltiplicazione delle matrici 500000, e ho notato che era ... senza fine (es. con eccezione di memoria esaurita dopo 10 minuti circa)! Dopo aver studiato haskell un po 'di più sono riuscito a liberarmi della pigrizia e il risultato migliore che sono riuscito a ottenere è ~ 20 volte più lento del suo equivalente in C. Quindi, la domanda: potresti rivedere il codice qui sotto e dire se le sue prestazioni possono essere migliorato un po 'di più? 20 volte mi sta ancora deludendo un po '.prestazioni di implemetazione della matrice haskell

import Prelude hiding (foldr, foldl, product) 
import Data.Monoid 
import Data.Foldable 

import Text.Printf 
import System.CPUTime 

import System.Environment 

data Vector a = Vec3 a a a 
       | Vec4 a a a a 
       deriving Show 

instance Foldable Vector where 
    foldMap f (Vec3 a b c) = f a `mappend` f b `mappend` f c 
    foldMap f (Vec4 a b c d) = f a `mappend` f b `mappend` f c `mappend` f d 

data Matr a = Matr !a !a !a !a 
        !a !a !a !a 
        !a !a !a !a 
        !a !a !a !a 

instance Show a => Show (Matr a) where 
    show m = foldr f [] $ matrRows m 
      where f a b = show a ++ "\n" ++ b 

matrCols (Matr a0 b0 c0 d0 a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3) 
       = [Vec4 a0 a1 a2 a3, Vec4 b0 b1 b2 b3, Vec4 c0 c1 c2 c3, Vec4 d0 d1 d2 d3] 

matrRows (Matr a0 b0 c0 d0 a1 b1 c1 d1 a2 b2 c2 d2 a3 b3 c3 d3) 
       = [Vec4 a0 b0 c0 d0, Vec4 a1 b1 c1 d1, Vec4 a2 b2 c2 d2, Vec4 a3 b3 c3 d3] 

matrFromList [a0, b0, c0, d0, a1, b1, c1, d1, a2, b2, c2, d2, a3, b3, c3, d3] 
     = Matr a0 b0 c0 d0 
       a1 b1 c1 d1 
       a2 b2 c2 d2 
       a3 b3 c3 d3 

matrId :: Matr Double 
matrId = Matr 1 0 0 0 
       0 1 0 0 
       0 0 1 0 
       0 0 0 1 

normalise (Vec4 x y z w) = Vec4 (x/w) (y/w) (z/w) 1 

mult a b = matrFromList [f r c | r <- matrRows a, c <- matrCols b] where 
      f a b = foldr (+) 0 $ zipWith (*) (toList a) (toList b) 
+4

vostra matrice 'mult' sta cambiando rappresentazioni tra matrici, liste e torna a matrici. Mentre questo ti permette di fare il calcolo in due righe di codice, sarà molto, molto lento. –

+2

@stephen tetley: non è necessariamente così.Al giorno d'oggi i compilatori sono piuttosto abili nell'inlining e nella deforestazione, quindi un compilatore intelligente eliderebbe le liste intermedie. Nei miei test questa implementazione, rispetto a una moltiplicazione "veloce" scritta a mano, non è terribilmente lenta (solo circa 1,5 volte più lenta). UPD: il mio errore, in realtà 6 volte più lento per le matrici doppie. –

+1

A rischio di essere etichettato come un troll, non penso che con questi test troverai risultati come quelli che stai cercando. Generalmente il migliore Haskell darà prestazioni al pari di C. buono. I grandi vantaggi di Haskell derivano dalla leggibilità, più espressività rispetto a C e librerie avanzate come 'STM' e' parallel', che rendono l'aggiunta del parallelismo molto facile rispetto alla maggior parte altre lingue. Per il puro lavoro numerico, è abbastanza difficile per le prestazioni di Haskell superare le implementazioni di C. –

risposta

8

In primo luogo, dubito che con questa implementazione otterrai prestazioni stellari. Ci sono troppe conversioni tra diverse rappresentazioni. Faresti meglio a basare il tuo codice su qualcosa come il pacchetto vector. Inoltre non fornisci tutto il tuo codice di test, quindi probabilmente ci sono altri problemi che non possiamo qui. Questo perché la pipeline della produzione al consumo ha un grande impatto sulle prestazioni di Haskell e non hai fornito nessuna delle due estremità.

Ora, due problemi specifici:

1) Il suo vettore è definito sia come vettore 3 o 4 elementi. Ciò significa che per ogni vettore è disponibile un controllo extra per vedere quanti elementi sono in uso. In C, immagino che la tua implementazione sia probabilmente più vicina a

struct vec { 
    double *vec; 
    int length; 
} 

Si dovrebbe fare qualcosa di simile in Haskell; questo è il modo in cui sono implementati vector e bytestring.

Anche se non si modifica la definizione Vector, rendere i campi rigorosi. Dovresti anche aggiungere i prapmi UNPACK (a Vector e Matrix) o compilare con -funbox-strict-fields.

2) Variazione mult a

mult a b = matrFromList [f r c | r <- matrRows a, c <- matrCols b] where 
      f a b = Data.List.foldl' (+) 0 $ zipWith (*) (toList a) (toList b) 

Il rigore extra di foldl' darà molto migliore prestazione in questo caso di foldr.

Questo cambiamento da solo potrebbe fare una grande differenza, ma senza vedere il resto del codice è difficile da dire.

+0

, forse il tipo di dati Array è più efficace. ci sono anche unboxed (UArray) e qualcos'altro per prestazioni migliori. – Nybble

+1

@Wu Xingbo - usare UArray sarebbe quasi certamente migliore. 'repa' sarebbe ancora meglio. –

+3

il pacchetto 'hmatrix' fornisce collegamenti a BLAS e LAPACK in un ambiente puramente funzionale. La rappresentazione dei dati è quella di 'Data.Vector.Storable' dal pacchetto' vector'. Dovresti scoprire che questo pacchetto è paragonabile a C con i benefici aggiunti che Haskell porta. – vivian

4

Rispondendo alla mia domanda solo per condividere nuovi risultati che ho ottenuto ieri:

  1. ho aggiornato GHC alla versione più recente e le prestazioni è diventato in effetti non è così male (solo ~ 7 volte peggio).

  2. Inoltre, ho provato a implementare la matrice in modo stupido e semplice (vedere l'elenco seguente) e ho ottenuto prestazioni davvero accettabili - solo 2 volte più lentamente di equivalente C.

    data Matr a = Matr (a, a, a, a 
            , a, a, a, a 
            , a, a, a, a 
            , a, a, a, a) 
    
    mult (Matr (!a0, !b0, !c0, !d0, 
          !a1, !b1, !c1, !d1, 
          !a2, !b2, !c2, !d2, 
          !a3, !b3, !c3, !d3)) 
        (Matr (!a0', !b0', !c0', !d0', 
          !a1', !b1', !c1', !d1', 
          !a2', !b2', !c2', !d2', 
          !a3', !b3', !c3', !d3')) 
        = Matr (a0'', b0'', c0'', d0'' 
          , a1'', b1'', c1'', d1'' 
          , a2'', b2'', c2'', d2'' 
          , a3'', b3'', c3'', d3'') 
         where a0'' = a0 * a0' + b0 * a1' + c0 * a2' + d0 * a3' 
           b0'' = a0 * b0' + b0 * b1' + c0 * b2' + d0 * b3' 
           c0'' = a0 * c0' + b0 * c1' + c0 * c2' + d0 * c3' 
           d0'' = a0 * d0' + b0 * d1' + c0 * d2' + d0 * d3' 
    
           a1'' = a1 * a0' + b1 * a1' + c1 * a2' + d1 * a3' 
           b1'' = a1 * b0' + b1 * b1' + c1 * b2' + d1 * b3' 
           c1'' = a1 * c0' + b1 * c1' + c1 * c2' + d1 * c3' 
           d1'' = a1 * d0' + b1 * d1' + c1 * d2' + d1 * d3' 
    
           a2'' = a2 * a0' + b2 * a1' + c2 * a2' + d2 * a3' 
           b2'' = a2 * b0' + b2 * b1' + c2 * b2' + d2 * b3' 
           c2'' = a2 * c0' + b2 * c1' + c2 * c2' + d2 * c3' 
           d2'' = a2 * d0' + b2 * d1' + c2 * d2' + d2 * d3' 
    
           a3'' = a3 * a0' + b3 * a1' + c3 * a2' + d3 * a3' 
           b3'' = a3 * b0' + b3 * b1' + c3 * b2' + d3 * b3' 
           c3'' = a3 * c0' + b3 * c1' + c3 * c2' + d3 * c3' 
           d3'' = a3 * d0' + b3 * d1' + c3 * d2' + d3 * d3' 
    
+1

Inoltre, prova a rendere rigorosi i campi di Matrix (nella definizione dei dati, aggiungi un! Davanti a ogni a, puoi lasciare fuori il botto nella funzione, quindi) e compilare con -funbox-strict-fields (o usare {- # UNPACK # -} direttiva). Manualmente {- # SPECIALIZE # -} ing mult per il tipo che usi effettivamente potrebbe valere anche la pena di provare. Haskell dovrebbe uscire testa a testa con C, quindi. – barsoap

+0

Il parametro del costruttore di dati Matrix è una tupla e se aggiungo un botto ad esso verrà valutata solo la sua testa. Posso aggiungere scoppi anche nella tupla? Sembra che GHC non mangi questo. Ecco perché non l'ho aggiunto nel costruttore di dati. – Maxym

Problemi correlati