2011-07-20 10 views
7

He oído mucho sobre el rendimiento increíble de los programas escritos en Haskell, y quería hacer algunas pruebas. Entonces, escribí una 'biblioteca' para las operaciones de la matriz simplemente para comparar su rendimiento con las mismas cosas escritas en puro C. Antes que nada, probé el rendimiento de multiplicación de 500000 matrices y noté que era ... interminable (es decir, terminando ¡con excepción de falta de memoria después de 10 minutos de eso)! Después de estudiar Haskell un poco más me las arreglé para deshacerme de la pereza y el mejor resultado que logré obtener es ~ 20 veces más lento que su equivalente en C. Entonces, la pregunta: ¿podría revisar el siguiente código y decir si su rendimiento puede mejorar un poco más? 20 veces todavía me decepciona un poco.ejecución de la matriz de ejecución 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

Su matriz 'mult' está cambiando representaciones entre matrices, listas y de nuevo a matrices. Si bien esto le permite hacer el cálculo en dos líneas de código, será muy, muy lento. –

+2

@stephen tetley: no es necesariamente así.En la actualidad, los compiladores son bastante buenos en la creación de líneas y la deforestación, por lo que un compilador inteligente podría eludir las listas intermedias. En mis pruebas, esta implementación, en comparación con una multiplicación desenrollada a mano "rápida", no es terriblemente lenta (apenas 1,5 veces más lenta). UPD: mi error, en realidad 6 veces más lento para matrices dobles. –

+1

A riesgo de ser etiquetado como un troll, no creo que encuentre resultados como los que está buscando con estas pruebas. En general, el mejor Haskell tendrá un rendimiento comparable con el buen C. Las grandes ventajas de Haskell provienen de la legibilidad, más expresividad en comparación con C y bibliotecas avanzadas como 'STM' y' parallel', que hacen que agregar paralelismo sea muy fácil en comparación con la mayoría otros idiomas. Para el trabajo numérico puro, es bastante difícil para el rendimiento de Haskell superar las implementaciones de C. –

Respuesta

8

En primer lugar, dudo que alguna vez obtenga un rendimiento estelar con esta implementación. Hay demasiadas conversiones entre diferentes representaciones. Sería mejor que basara su código en algo como el paquete vector. Además, no proporciona todos los códigos de prueba, por lo que probablemente haya otros problemas que no podemos resolver aquí. Esto se debe a que el flujo de producción al consumo tiene un gran impacto en el rendimiento de Haskell, y usted no ha proporcionado ninguno de los extremos.

Ahora, dos problemas específicos:

1) Su vector se define como un vector de 3 o 4 elemento. Esto significa que para cada vector hay una verificación adicional para ver cuántos elementos están en uso. En C, imagino que su implementación probablemente esté más cerca de

struct vec { 
    double *vec; 
    int length; 
} 

Debe hacer algo similar en Haskell; así es como se implementan vector y bytestring por ejemplo.

Incluso si no cambia la definición Vector, haga que los campos sean estrictos. También debe agregar UNPACK pragmas (a Vector y Matrix) o compilar con -funbox-strict-fields.

2) Cambio 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) 

La rigidez extra de foldl' dará un rendimiento mucho mejor en este caso que foldr.

Este cambio solo puede marcar una gran diferencia, pero sin ver el resto de su código es difícil de decir.

+0

tal vez el tipo de datos Array es más efectivo. también hay unboxed (UArray) y algo más para un mejor rendimiento. – Nybble

+1

@Wu Xingbo: usar UArray seguramente sería mejor. 'repa' sería mejor aún. –

+3

el paquete 'hmatrix' proporciona enlaces a BLAS y LAPACK en una configuración puramente funcional. La representación de datos es la de 'Data.Vector.Storable' del paquete' vector'. Debería encontrar que este paquete es comparable a C con los beneficios adicionales que trae Haskell. – vivian

4

responder a mi propia pregunta sólo para compartir nuevos resultados que obtuve ayer:

  1. He actualizado GHC a la versión más reciente y el rendimiento se convirtió en verdad no es tan malo (sólo ~ 7 veces peor).

  2. También traté de implementar la matriz de una manera estúpida y simple (ver el listado a continuación) y obtuve un rendimiento realmente aceptable, solo que 2 veces más lento que el equivalente en 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

Además, intente hacer los campos de Matrix strict (en la definición de datos, agregue a! Delante de cada a, puede omitir los bangs en la función, luego) y compilar con -funbox-strict-fields (o use {- # UNPACK # -} pragmas). Manualmente {- # SPECIALIZE # -} ing mult al tipo que realmente usa podría valer la pena probarlo también. Haskell debería salir cara a cara con C, entonces. – barsoap

+0

El parámetro del constructor de datos de matriz es una tupla, y si agrego un bang solo se evaluará su encabezado. ¿Puedo agregar flequillo dentro de la tupla también? Parece que GHC no come esto. Es por eso que no lo agregué al constructor de datos. – Maxym

Cuestiones relacionadas