2012-02-23 14 views
15

Tengo una aplicación que dedica aproximadamente el 80% de su tiempo a calcular el centroide de una lista grande (10^7) de vectores de alta dimensión (dim = 100) usando Kahan summation algorithm. He hecho todo lo posible para optimizar la suma, pero sigue siendo 20 veces más lenta que una implementación de C equivalente. La creación de perfiles indica que los culpables son las funciones unsafeRead y unsafeWrite de Data.Vector.Unboxed.Mutable. Mi pregunta es: ¿estas funciones son realmente tan lentas o estoy malinterpretando las estadísticas de creación de perfiles?¿La indexación de Data.Vector.Unboxed.Mutable.MVector es realmente tan lenta?

Aquí están las dos implementaciones. El Haskell está compilado con ghc-7.0.3 usando el servidor de fondo llvm. El C se compila con llvm-gcc.

suma Kahan en Haskell:

{-# LANGUAGE BangPatterns #-} 
module Test where 

import Control.Monad (mapM_) 
import Data.Vector.Unboxed (Vector, Unbox) 
import Data.Vector.Unboxed.Mutable (MVector) 
import qualified Data.Vector.Unboxed as U 
import qualified Data.Vector.Unboxed.Mutable as UM 
import Data.Word (Word) 
import Data.Bits (shiftL, shiftR, xor) 

prng :: Word -> Word 
prng w = w' where 
    !w1 = w `xor` (w `shiftL` 13) 
    !w2 = w1 `xor` (w1 `shiftR` 7) 
    !w' = w2 `xor` (w2 `shiftL` 17) 

mkVect :: Word -> Vector Double 
mkVect = U.force . U.map fromIntegral . U.fromList . take 100 . iterate prng 

foldV :: (Unbox a, Unbox b) 
     => (a -> b -> a) -- componentwise function to fold 
     -> Vector a  -- initial accumulator value 
     -> [Vector b] -- data vectors 
     -> Vector a  -- final accumulator value 
foldV fn accum vs = U.modify (\x -> mapM_ (liftV fn x) vs) accum where 
    liftV f acc = fV where 
     fV v = go 0 where 
      n = min (U.length v) (UM.length acc) 
      go i | i < n  = step >> go (i + 1) 
       | otherwise = return() 
       where 
        step = {-# SCC "fV_step" #-} do 
         a <- {-# SCC "fV_read" #-} UM.unsafeRead acc i 
         b <- {-# SCC "fV_index" #-} U.unsafeIndexM v i 
         {-# SCC "fV_write" #-} UM.unsafeWrite acc i $! {-# SCC "fV_apply" #-} f a b 

kahan :: [Vector Double] -> Vector Double 
kahan [] = U.singleton 0.0 
kahan (v:vs) = fst . U.unzip $ foldV kahanStep acc vs where 
    acc = U.map (\z -> (z, 0.0)) v 

kahanStep :: (Double, Double) -> Double -> (Double, Double) 
kahanStep (s, c) x = (s', c') where 
    !y = x - c 
    !s' = s + y 
    !c' = (s' - s) - y 
{-# NOINLINE kahanStep #-} 

zero :: U.Vector Double 
zero = U.replicate 100 0.0 

myLoop n = kahan $ map mkVect [1..n] 

main = print $ myLoop 100000 

Compilar con ghc-7.0.3 usando el backend llvm:

ghc -o Test_hs --make -fforce-recomp -O3 -fllvm -optlo-O3 -msse2 -main-is Test.main Test.hs 

time ./Test_hs 
real 0m1.948s 
user 0m1.936s 
sys  0m0.008s 

información de perfil:

16,710,594,992 bytes allocated in the heap 
     33,047,064 bytes copied during GC 
      35,464 bytes maximum residency (1 sample(s)) 
      23,888 bytes maximum slop 
       1 MB total memory in use (0 MB lost due to fragmentation) 

    Generation 0: 31907 collections,  0 parallel, 0.28s, 0.27s elapsed 
    Generation 1:  1 collections,  0 parallel, 0.00s, 0.00s elapsed 

    INIT time 0.00s ( 0.00s elapsed) 
    MUT time 24.73s (24.74s elapsed) 
    GC time 0.28s ( 0.27s elapsed) 
    RP time 0.00s ( 0.00s elapsed) 
    PROF time 0.00s ( 0.00s elapsed) 
    EXIT time 0.00s ( 0.00s elapsed) 
    Total time 25.01s (25.02s elapsed) 

    %GC time  1.1% (1.1% elapsed) 

    Alloc rate 675,607,179 bytes per MUT second 

    Productivity 98.9% of total user, 98.9% of total elapsed 

    Thu Feb 23 02:42 2012 Time and Allocation Profiling Report (Final) 

     Test_hs +RTS -s -p -RTS 

    total time =  24.60 secs (1230 ticks @ 20 ms) 
    total alloc = 8,608,188,392 bytes (excludes profiling overheads) 

COST CENTRE     MODULE    %time %alloc 

fV_write      Test     31.1 26.0 
fV_read      Test     27.2 23.2 
mkVect       Test     12.3 27.2 
fV_step      Test     11.7 0.0 
foldV       Test     5.9 5.7 
fV_index      Test     5.2 9.3 
kahanStep      Test     3.3 6.5 
prng       Test     2.2 1.8 


                           individual inherited 
COST CENTRE    MODULE            no. entries %time %alloc %time %alloc 

MAIN      MAIN             1   0 0.0 0.0 100.0 100.0 
CAF:main1    Test             339   1 0.0 0.0  0.0 0.0 
    main     Test             346   1 0.0 0.0  0.0 0.0 
CAF:main2    Test             338   1 0.0 0.0 100.0 100.0 
    main     Test             347   0 0.0 0.0 100.0 100.0 
    myLoop    Test             348   1 0.2 0.2 100.0 100.0 
    mkVect    Test             350  400000 12.3 27.2 14.5 29.0 
    prng    Test             351  9900000 2.2 1.8  2.2 1.8 
    kahan    Test             349   102 0.0 0.0 85.4 70.7 
    foldV    Test             359   1 5.9 5.7 85.4 70.7 
     fV_step   Test             360  9999900 11.7 0.0 79.5 65.1 
     fV_write   Test             367 19999800 31.1 26.0 35.4 32.5 
     fV_apply   Test             368  9999900 1.0 0.0  4.3 6.5 
     kahanStep  Test             369  9999900 3.3 6.5  3.3 6.5 
     fV_index   Test             366  9999900 5.2 9.3  5.2 9.3 
     fV_read   Test             361  9999900 27.2 23.2 27.2 23.2 
CAF:lvl19_r3ei   Test             337   1 0.0 0.0  0.0 0.0 
    kahan     Test             358   0 0.0 0.0  0.0 0.0 
CAF:poly_$dPrimMonad3_r3eg Test             336   1 0.0 0.0  0.0 0.0 
    kahan     Test             357   0 0.0 0.0  0.0 0.0 
CAF:$dMVector2_r3ee  Test             335   1 0.0 0.0  0.0 0.0 
CAF:$dVector1_r3ec  Test             334   1 0.0 0.0  0.0 0.0 
CAF:poly_$dMonad_r3ea Test             333   1 0.0 0.0  0.0 0.0 
CAF:$dMVector1_r3e2  Test             330   1 0.0 0.0  0.0 0.0 
CAF:poly_$dPrimMonad2_r3e0 Test             328   1 0.0 0.0  0.0 0.0 
    foldV     Test             365   0 0.0 0.0  0.0 0.0 
CAF:lvl11_r3dM   Test             322   1 0.0 0.0  0.0 0.0 
    kahan     Test             354   0 0.0 0.0  0.0 0.0 
CAF:lvl10_r3dK   Test             321   1 0.0 0.0  0.0 0.0 
    kahan     Test             355   0 0.0 0.0  0.0 0.0 
CAF:$dMVector_r3dI  Test             320   1 0.0 0.0  0.0 0.0 
    kahan     Test             356   0 0.0 0.0  0.0 0.0 
CAF      GHC.Float           297   1 0.0 0.0  0.0 0.0 
CAF      GHC.IO.Handle.FD          256   2 0.0 0.0  0.0 0.0 
CAF      GHC.IO.Encoding.Iconv        214   2 0.0 0.0  0.0 0.0 
CAF      GHC.Conc.Signal          211   1 0.0 0.0  0.0 0.0 
CAF      Data.Vector.Generic         182   1 0.0 0.0  0.0 0.0 
CAF      Data.Vector.Unboxed         174   2 0.0 0.0  0.0 0.0 

memory residency by cost center memory residency by type for <code>foldV</code> memory residency by type for <code>unsafeRead</code> memory residency by type for <code>unsafeWrite</code>

La aplicación equivalente en C:

#include <stdint.h> 
#include <stdio.h> 


#define VDIM 100 
#define VNUM 100000 



uint64_t prng (uint64_t w) { 
    w ^= w << 13; 
    w ^= w >> 7; 
    w ^= w << 17; 
    return w; 
}; 

void kahanStep (double *s, double *c, double x) { 
    double y, t; 
    y = x - *c; 
    t = *s + y; 
    *c = (t - *s) - y; 
    *s = t; 
} 

void kahan(double s[], double c[]) { 
    for (int i = 1; i <= VNUM; i++) { 
     uint64_t w = i; 
     for (int j = 0; j < VDIM; j++) { 
       kahanStep(&s[j], &c[j], w); 
       w = prng(w); 
     } 
    } 
}; 


int main (int argc, char* argv[]) { 
    double acc[VDIM], err[VDIM]; 
    for (int i = 0; i < VDIM; i++) { 
     acc[i] = err[i] = 0.0; 
    }; 
    kahan(acc, err); 
    printf("[ "); 
    for (int i = 0; i < VDIM; i++) { 
     printf("%g ", acc[i]); 
    }; 
    printf("]\n"); 
}; 

compilado con gcc-llvm:

>llvm-gcc -o Test_c -O3 -msse2 -std=c99 test.c 

>time ./Test_c 
real 0m0.096s 
user 0m0.088s 
sys  0m0.004s 

Actualización 1: anulo la inlined kahanStep en la versión C. Apenas hizo mella en el rendimiento. Espero que ahora todos podamos reconocer la ley de Amdahl y seguir adelante. Como ineficiente como kahanStep podría ser, unsafeRead y unsafeWrite son 9-10 veces más lentos. Esperaba que alguien pudiera arrojar algo de luz sobre las posibles causas de ese hecho.

Además, debo decir que desde que estoy interactuando con una biblioteca que utiliza Data.Vector.Unboxed, así que estoy un poco casado con ella en este punto, y separarse de él sería muy traumático :-)

Actualización 2 : Supongo que no estaba lo suficientemente claro en mi pregunta original. No estoy buscando formas de acelerar este microbenchmark. Estoy buscando una explicación de las estadísticas del perfil intuitivo, así que puedo decidir si presentar un informe de error contra vector.

+2

Estas dos implementaciones son de ninguna manera equivalente. Además, ¿por qué 'kahanStep' NOINLINE'd? INLINE'ing 'kahanStep',' foldV', y 'mkVect' hacen algo de la diferencia, pero aún es bastante más lenta que la versión C para mí. –

+0

post para con GHC gráficos de perfiles (PNG) http://stackoverflow.com/questions/5939630/best-way-of-looping-over-a-2-d-array-using-repa –

+0

kahanStep es NOINLINED, porque de lo contrario no aparece en la información de perfil para mí. Si lo alineo, voy de 20 veces más lento que C a 19.5x. Ese no es el problema. Si observa la información de perfiles, puede ver que el costo 'fV_read' y' fV_write' centra cada cuenta durante ~ 30% del tiempo, mientras que 'kahaneStep' solo recibe un crédito del 3,3%. Ese es el problema: el cálculo real es solo una parte menor del tiempo de ejecución. – Alinabi

Respuesta

15

Su versión C es no equivalente a su implementación de Haskell. En C, ha indicado usted mismo el importante paso de suma de Kahan, en Haskell creó una función polimórfica de orden superior que hace mucho más y toma el paso de transformación como parámetro. Mover kahanStep a una función separada en C no es el punto, aún estará marcado por el compilador. Incluso si lo coloca en su propio archivo fuente, compila por separado y lo vincula sin optimización de tiempo de enlace, solo ha abordado parte de la diferencia.

he hecho una versión C que está más cerca de la versión Haskell,

Kahan.h:

typedef struct DPair_st { 
    double fst, snd; 
    } DPair; 

DPair kahanStep(DPair pr, double x); 

kahanStep.c:

#include "kahan.h" 

DPair kahanStep (DPair pr, double x) { 
    double y, t; 
    y = x - pr.snd; 
    t = pr.fst + y; 
    pr.snd = (t - pr.fst) - y; 
    pr.fst = t; 
    return pr; 
} 

main.c:

#include <stdint.h> 
#include <stdio.h> 
#include "kahan.h" 


#define VDIM 100 
#define VNUM 100000 

uint64_t prng (uint64_t w) { 
    w ^= w << 13; 
    w ^= w >> 7; 
    w ^= w << 17; 
    return w; 
}; 

void kahan(double s[], double c[], DPair (*fun)(DPair,double)) { 
    for (int i = 1; i <= VNUM; i++) { 
     uint64_t w = i; 
     for (int j = 0; j < VDIM; j++) { 
      DPair pr; 
      pr.fst = s[j]; 
      pr.snd = c[j]; 
      pr = fun(pr,w); 
      s[j] = pr.fst; 
      c[j] = pr.snd; 
      w = prng(w); 
     } 
    } 
}; 


int main (int argc, char* argv[]) { 
    double acc[VDIM], err[VDIM]; 
    for (int i = 0; i < VDIM; i++) { 
     acc[i] = err[i] = 0.0; 
    }; 
    kahan(acc, err,kahanStep); 
    printf("[ "); 
    for (int i = 0; i < VDIM; i++) { 
     printf("%g ", acc[i]); 
    }; 
    printf("]\n"); 
}; 

compilan por separado y unidos, que corre alrededor de 25% más lenta que la primera versión C aquí (0,1 s vs. 0.079s).

Ahora tiene una función de orden superior en C, considerablemente más lenta que la original, pero mucho más rápida que el código de Haskell. Una diferencia importante es que la función C toma un par unboxed de double sy un double sin casillas como argumentos, mientras que el Haskell kahanStep toma un par de cajas de Double sy Double en caja y devuelve un par de cajas de Double s, que requieren un costoso boxeo y desempaquetado en el bucle foldV. Eso es direccionable por más inline. Inventar explícitamente foldV, kahanStep y step baja el tiempo de 0.90s a 0.74s aquí con ghc-7.0.4 (tiene un efecto menor en la salida de ghc-7.4.1, de 0.99 a 0.90).

Pero el boxeo y el desempaquetado es, por desgracia, la parte más pequeña de la diferencia. foldV hace mucho más que C kahan, toma una lista de vectores utilizada para modificar el acumulador. Esa lista de vectores está completamente ausente en el código C, y eso hace una gran diferencia. Todos estos 100000 vectores tienen que asignarse, cumplimentarse y colocarse en una lista (debido a la pereza, no todos están simultáneamente vivos, por lo que no hay problema de espacio, pero ellos, así como las celdas de la lista, deben asignarse y la basura recogido, lo que lleva un tiempo considerable). Y en el bucle propiamente dicho, en lugar de tener un Word# pasado en un registro, el valor precalculado se lee del vector.

Si utiliza una traducción más directa de la C a Haskell,

{-# LANGUAGE CPP, BangPatterns #-} 
module Main (main) where 

#define VDIM 100 
#define VNUM 100000 

import Data.Array.Base 
import Data.Array.ST 
import Data.Array.Unboxed 
import Control.Monad.ST 
import GHC.Word 
import Control.Monad 
import Data.Bits 

prng :: Word -> Word 
prng w = w' 
    where 
    !w1 = w `xor` (w `shiftL` 13) 
    !w2 = w1 `xor` (w1 `shiftR` 7) 
    !w' = w2 `xor` (w2 `shiftL` 17) 

type Vec s = STUArray s Int Double 

kahan :: Vec s -> Vec s -> ST s() 
kahan s c = do 
    let inner w j 
      | j < VDIM = do 
       !cj <- unsafeRead c j 
       !sj <- unsafeRead s j 
       let !y = fromIntegral w - cj 
        !t = sj + y 
        !w' = prng w 
       unsafeWrite c j ((t-sj)-y) 
       unsafeWrite s j t 
       inner w' (j+1) 
      | otherwise = return() 
    forM_ [1 .. VNUM] $ \i -> inner (fromIntegral i) 0 

calc :: ST s (Vec s) 
calc = do 
    s <- newArray (0,VDIM-1) 0 
    c <- newArray (0,VDIM-1) 0 
    kahan s c 
    return s 

main :: IO() 
main = print . elems $ runSTUArray calc 

es mucho más rápido. Es cierto que sigue siendo unas tres veces más lento que el C, pero el original era 13 veces más lento aquí (y no tengo instalado llvm, entonces uso vacc gcc y el nativo respaldado de GHC, usando llvm puede dar resultados ligeramente diferentes).

No creo que la indexación sea realmente la culpable. El paquete vectorial se basa en gran medida en la magia del compilador, pero la compilación para el soporte de perfiles interfiere masivamente con eso. Para paquetes como vector o bytestring que utilizan su propio marco de fusión para la optimización, la interferencia de generación de perfiles puede ser bastante desastrosa y el perfilado resulta completamente inútil. Me inclino a creer que tenemos un caso así aquí.

en el núcleo, todas las lecturas y escrituras se transforman a los primops readDoubleArray#, indexDoubleArray# y writeDoubleArray#, que son rápido. Tal vez un poco más lento que un acceso de matriz C, pero no mucho. Así que estoy seguro de que ese no es el problema y la causa de la gran diferencia. Pero ha puesto anotaciones {-# SCC#-} en ellas, por lo que deshabilita cualquier optimización que implique una reorganización de cualquiera de esos términos. Y cada vez que se ingresa uno de estos puntos, debe registrarse. No estoy lo suficientemente familiarizado con el generador de perfiles y el optimizador para saber exactamente qué sucede, pero, como punto de datos, con los pragmas {-# INLINE #-} en foldV, step y kahanStep, una ejecución de perfiles con estos SCC tomó 3.17s, y con los SCC fV_step, fV_read, fV_index, fV_write y fV_apply eliminados (nada más cambió) una ejecución de creación de perfiles tomó solo 2,03 s (ambas veces según lo informado por +RTS -P, por lo que se resta la sobrecarga del perfil). Esa diferencia muestra que los SCC en funciones baratas y los SCC de grano muy fino pueden sesgar masivamente los resultados de los perfiles. Ahora bien, si también ponemos {-# INLINE #-} pragmas en mkVect, kahan y prng, nos queda un perfil completamente desinformativo, pero la ejecución demora solo 1.23s. (Sin embargo, estos últimos entrantes no tienen ningún efecto para las ejecuciones sin perfil, sin creación de perfiles, están inlineados automáticamente.)

Por lo tanto, no tome los resultados del perfil como verdades incuestionables. Cuanto más dependa su código (directa o indirectamente a través de las bibliotecas) de las optimizaciones, más vulnerable será a los resultados de creación de perfiles engañosos causados ​​por las optimizaciones desactivadas. Esto también se cumple, pero en un grado mucho menor, para que los perfiles de montón delimiten las fugas de espacio.

Cuando tenga un resultado de perfil sospechoso, compruebe lo que sucede cuando elimina algunos SCC. Si eso resulta en una gran caída del tiempo de ejecución, ese SCC no fue su problema principal (puede volver a ser un problema una vez que se hayan solucionado otros problemas).

Mirando el core generado para su programa, lo que saltó fue que su kahanStep - dicho sea de paso, quitar el {-# NOINLINE #-} pragma de eso, es contraproducente - produce un par caja de caja Double s en el circuito, que fue inmediatamente deconstruido y los componentes desempaquetados. Tal boxeo intermedio innecesario de valores es costoso y ralentiza mucho los cálculos.


Como este vino arriba en haskell-cafe de nuevo hoy, donde alguien se desempeño terrible en el código anterior con GHC-7.4.1, tibbe se encargó de investigar el núcleo que produjo GHC GHC y encontraron que produce código subóptima para la conversión de Word a Double. Reemplazando el fromIntegral de la conversión con una conversión personalizada usando solamente primitivas (envueltas) (y eliminando los patrones de explosión que no hacen la diferencia aquí, el analizador de rigor de GHC es suficientemente bueno para ver a través del algoritmo, debería aprender a confiar más en él ;), obtenemos una versión que está a la par con la salida gcc -O3 's para el original C:

{-# LANGUAGE CPP #-} 
module Main (main) where 

#define VDIM 100 
#define VNUM 100000 

import Data.Array.Base 
import Data.Array.ST 
import Data.Array.Unboxed 
import Control.Monad.ST 
import GHC.Word 
import Control.Monad 
import Data.Bits 
import GHC.Float (int2Double) 

prng :: Word -> Word 
prng w = w' 
    where 
    w1 = w `xor` (w `shiftL` 13) 
    w2 = w1 `xor` (w1 `shiftR` 7) 
    w' = w2 `xor` (w2 `shiftL` 17) 

type Vec s = STUArray s Int Double 

kahan :: Vec s -> Vec s -> ST s() 
kahan s c = do 
    let inner w j 
      | j < VDIM = do 
       cj <- unsafeRead c j 
       sj <- unsafeRead s j 
       let y = word2Double w - cj 
        t = sj + y 
        w' = prng w 
       unsafeWrite c j ((t-sj)-y) 
       unsafeWrite s j t 
       inner w' (j+1) 
      | otherwise = return() 
    forM_ [1 .. VNUM] $ \i -> inner (fromIntegral i) 0 

calc :: ST s (Vec s) 
calc = do 
    s <- newArray (0,VDIM-1) 0 
    c <- newArray (0,VDIM-1) 0 
    kahan s c 
    return s 

correction :: Double 
correction = 2 * int2Double minBound 

word2Double :: Word -> Double 
word2Double w = case fromIntegral w of 
        i | i < 0 -> int2Double i - correction 
        | otherwise -> int2Double i 

main :: IO() 
main = print . elems $ runSTUArray calc 
+0

Bien, gracias por su problema, pero respondió una pregunta que no hice (vea mi actualización). Los tiempos son para el programa compilado _without_ perfiles de apoyo, así que esto no puede ser un problema con la mala interacción entre 'vECTOR' y el perfilador – Alinabi

+0

El punto de la parte sobre perfiles era que sus resultados de descripción son probablemente inútil. Tenga en cuenta que los tiempos de generación de perfiles son aproximadamente 12 veces más altos que los que no generan perfiles, es un factor inusualmente alto. Investigando un poco más en una actualización de la respuesta, tenga un poco de paciencia, soy un escritor lento. –

+0

¿Entonces está diciendo que el perfilado, en general, es inútil? Porque estoy preguntando por qué 'unsafeRead' y' unssafeWrite', con el perfil habilitado, son 10 veces más caros que 'kahanStep' también con el perfil habilitado. Si dices que no puedo confiar en esa medición debido a los gastos generales de los perfiles, entonces declaras que los profilers, en general, son inútiles. – Alinabi

4

Hay una mezcla divertida en la lista de combinadores en todo esto Data.Vector código aparentemente. Si hago la primera enmienda obvio, en sustitución de

mkVect = U.force . U.map fromIntegral . U.fromList . take 100 . iterate prng 

con el uso correcto de Data.Vector.Unboxed:

mkVect = U.force . U.map fromIntegral . U.iterateN 100 prng 

continuación, mi tiempo se reduce en dos tercios - real 0m1.306s-real 0m0.429s Parece que toda la las funciones de nivel superior tienen este problema, excepto prng y zero

+0

Eso reduce a la mitad el tiempo para mí (tuve que actualizar 'vector', la versión instalada por defecto en Ubuntu no tiene' iterateN') pero todo se debe a una mejora en 'mkVect' que no me interesa. No funciona ni explica el hecho de que mi programa gasta 10 veces más en 'unsafeRead' y' insafeWrite', luego lo hace en 'kahanStep' – Alinabi

+0

. Tu pensamiento es incorrecto. Este es solo un ejemplo independiente que captura un patrón que aparece en una aplicación real. No puedo publicar el código real porque es a) grande yb) cubierto por NDA. – Alinabi

+0

El núcleo de este módulo es 'foldV', que es un pliegue de lista. – applicative

1

Sé que no solicitó una forma de mejorar este microensayo, pero le daré una explicación de que mig Puede resultar útil al escribir bucles en el futuro:

Una llamada a función desconocida, como la realizada en el argumento de orden superior foldV, puede ser costosa cuando se realiza con frecuencia en un bucle. En particular, inhibirá el desempaquetado de los argumentos de la función, lo que lleva a una mayor asignación.La razón por la que inhibe el argumento de unboxing es que no sabemos que la función que estamos llamando es estricta en esos argumentos y, por lo tanto, pasamos los argumentos como, por ejemplo, (Double, Double), en lugar de como Double# -> Double#.

El compilador puede averiguar la información de rigor si el bucle (por ejemplo, foldV) se encuentra con el cuerpo del bucle (por ejemplo, kahanStep). Por esa razón, recomiendo a las personas INLINE funciones de orden superior. En este caso, inlining foldV y extracción de la NOINLINE en kahanStep mejora el tiempo de ejecución de un poco para mí.

Esto no hace que dicho rendimiento a la par con C en este caso, ya que hay otras cosas que suceden (como otros han comentado), pero es un paso en la dirección correcta (y que es un paso que puede hacer sin tener que mirar la salida de creación de perfiles).

3

Este apareció en las listas de correo y yo descubrimos que hay un error en la palabra-> código de conversión doble en GHC 7.4.1 (por lo menos). Esta versión, que trabaja alrededor del insecto, es tan rápido como el código C en mi máquina:

{-# LANGUAGE CPP, BangPatterns, MagicHash #-} 
module Main (main) where 

#define VDIM 100 
#define VNUM 100000 

import Control.Monad.ST 
import Data.Array.Base 
import Data.Array.ST 
import Data.Bits 
import GHC.Word 

import GHC.Exts 

prng :: Word -> Word 
prng w = w' 
    where 
    w1 = w `xor` (w `shiftL` 13) 
    w2 = w1 `xor` (w1 `shiftR` 7) 
    w' = w2 `xor` (w2 `shiftL` 17) 

type Vec s = STUArray s Int Double 

kahan :: Vec s -> Vec s -> ST s() 
kahan s c = do 
    let inner !w j 
      | j < VDIM = do 
       cj <- unsafeRead c j 
       sj <- unsafeRead s j 
       let y = word2Double w - cj 
        t = sj + y 
        w' = prng w 
       unsafeWrite c j ((t-sj)-y) 
       unsafeWrite s j t 
       inner w' (j+1) 
      | otherwise = return() 

     outer i | i <= VNUM = inner (fromIntegral i) 0 >> outer (i + 1) 
       | otherwise = return() 
    outer (1 :: Int) 

calc :: ST s (Vec s) 
calc = do 
    s <- newArray (0,VDIM-1) 0 
    c <- newArray (0,VDIM-1) 0 
    kahan s c 
    return s 

main :: IO() 
main = print . elems $ runSTUArray calc 

{- I originally used this function, which isn't quite correct. 
    We need a real bug fix in GHC. 
word2Double :: Word -> Double 
word2Double (W# w) = D# (int2Double# (word2Int# w)) 
-} 

correction :: Double 
correction = 2 * int2Double minBound 

word2Double :: Word -> Double 
word2Double w = case fromIntegral w of 
        i | i < 0 -> int2Double i - correction 
        | otherwise -> int2Double i 

Otras listas adicionales de trabajar en torno a la Palabra-> doble fallo, yo también he quitado para que coincida con la versión C mejor.

Cuestiones relacionadas