2012-09-21 16 views
15

Tengo un poco de conocimiento de haskell, pero siempre estoy un poco inseguro sobre qué tipo de pragmas y optimizaciones debo usar y dónde. Como¿Cuándo usar varios pragmas de lenguaje y optimizaciones?

  • Me gusta cuándo usar SPECIALIZE pragma y qué mejoras de rendimiento tiene.
  • Dónde usar RULES. Escucho que las personas toman sobre una regla particular que no se dispara? ¿Cómo lo verificamos?
  • Cuándo hacer estrictos los argumentos de una función y cuándo ayuda eso? Entiendo que hacer un argumento estricto hará que los argumentos sean evaluados a la forma normal, entonces ¿por qué no debería agregar rigor a todos los argumentos de la función? ¿Cómo decido?
  • ¿Cómo veo y verifico si tengo una fuga de espacio en mi programa? ¿Cuáles son los patrones generales que constituyen una fuga de espacio?
  • ¿Cómo puedo ver si hay un problema con demasiada pereza? Siempre puedo verificar el perfil del montón, pero quiero saber cuáles son las causas generales, los ejemplos y los patrones en los que la pereza duele.

¿Hay alguna fuente que se refiera a las optimizaciones avanzadas (tanto a niveles más altos como más bajos) especialmente para Haskell?

+0

RHW ch 25? http://book.realworldhaskell.org/read/profiling-and-optimization.html –

+0

@DonStewart Gracias .. Ya leí RWH .. El problema es que sé lo que hacen, pero no tengo una intuición sobre cuándo y donde su uso es importante y no importante. Hice esta pregunta para encontrar otras fuentes para mejorar mi comprensión. – Satvik

Respuesta

18

Me gusta cuándo usar SPECIALIZE pragma y qué mejoras de rendimiento tiene.

Deje que el compilador especialice una función si tiene una función (tipo de clase) polimórfica, y espera que se la invoque a menudo en una o varias instancias de la (s) clase (s).

La especialización elimina la búsqueda del diccionario donde se utiliza, y a menudo permite una mayor optimización, las funciones de miembros de la clase a menudo pueden incluirse y están sujetas a análisis de rigor, ambas ofrecen ganancias de rendimiento potencialmente enormes. Si la única optimización posible es la eliminación de la búsqueda dicitonary, la ganancia generalmente no será enorme.

A partir del GHC-7, probablemente sea más útil darle a la función un pragma {-# INLINABLE #-}, que hace que su fuente (casi sin modificar, normalizar y desgrasar) esté disponible en el archivo de interfaz, por lo que la función puede ser especializada y posiblemente incluso en línea en el sitio de llamadas.

Dónde usar RULES. Escucho que las personas toman sobre una regla particular que no se dispara? ¿Cómo lo verificamos?

Puede verificar qué reglas se activaron utilizando la opción de línea de comando -ddump-rule-firings. Por lo general, arroja una gran cantidad de reglas disparadas, por lo que debe buscar un poco sus propias reglas.

utiliza reglas

  • cuando se tiene una versión más eficiente de una función para tipos especiales, por ejemplo,

    {-# RULES 
    "realToFrac/Float->Double" realToFrac = float2Double 
        #-} 
    
  • cuando algunas de las funciones pueden ser reemplazados con una versión más eficiente para argumentos especiales, por ejemplo,

    {-# RULES 
    "^2/Int"  forall x. x^(2 :: Int) = let u = x in u*u 
    "^3/Int"  forall x. x^(3 :: Int) = let u = x in u*u*u 
    "^4/Int"  forall x. x^(4 :: Int) = let u = x in u*u*u*u 
    "^5/Int"  forall x. x^(5 :: Int) = let u = x in u*u*u*u*u 
    "^2/Integer" forall x. x^(2 :: Integer) = let u = x in u*u 
    "^3/Integer" forall x. x^(3 :: Integer) = let u = x in u*u*u 
    "^4/Integer" forall x. x^(4 :: Integer) = let u = x in u*u*u*u 
    "^5/Integer" forall x. x^(5 :: Integer) = let u = x in u*u*u*u*u 
        #-} 
    
  • al reescribir una expresión de acuerdo con las leyes generales podrían producir código que es mejor para optimizar, por ejemplo,

    {-# RULES 
    "map/map" forall f g. (map f) . (map g) = map (f . g) 
        #-} 
    

El uso extensivo de RULES en el segundo estilo se hace en marcos de fusión, por ejemplo en la biblioteca de text, y para las funciones de lista en base, un tipo diferente de fusión (foldr/build de fusión) se implementa usando reglas.

Cuándo hacer estrictos los argumentos de una función y cuándo ayuda eso? Entiendo que hacer un argumento estricto hará que los argumentos sean evaluados a la forma normal, entonces ¿por qué no debería agregar rigor a todos los argumentos de la función? ¿Cómo decido?

Haciendo un argumento estricta se asegurará de que se evalúa a débil cabeza de forma normal, no forma normal.

No hace todos los argumentos estrictos porque algunas funciones deben ser no estrictas en algunos de sus argumentos para funcionar y algunas son menos eficientes si son estrictas en todos los argumentos.

Para examplepartition no debe ser estricta en su segundo argumento a trabajar en absoluto en las listas infinitas, más general cada función usada en foldr debe ser no estricta en el segundo argumento para trabajar en listas infinitas. En listas finitas, tener la función no estricta en el segundo argumento puede hacerlo dramáticamente más eficiente (foldr (&&) True (False:replicate (10^9) True)).

Hace un argumento estricto, si sabe que el argumento debe ser evaluado antes de que se pueda hacer cualquier trabajo que valga la pena de todos modos. En muchos casos, el analizador de rigor de GHC puede hacerlo por sí mismo, pero, por supuesto, no en todos.

Un caso muy típico son los acumuladores en bucles o repeticiones de cola, donde la rigidez de adición impide la construcción de enormes thunks en el camino.

No conozco reglas estrictas sobre dónde añadir rigor, para mí es una cuestión de experiencia, después de un tiempo aprendes en qué lugares es probable que la suma de rigor ayude a dónde dañar.

Como regla general, tiene sentido mantener los datos pequeños (como Int) evaluados, pero hay excepciones.

¿Cómo puedo ver y verificar que tengo una pérdida de espacio en mi programa? ¿Cuáles son los patrones generales que constituyen una fuga de espacio?

El primer paso es usar la opción +RTS -s (si el programa estaba vinculado con rtsopts habilitado). Eso le muestra cuánta memoria se utilizó en general, y a menudo puede juzgar por eso si tiene una fuga. Se puede obtener un resultado más informativo ejecutando el programa con la opción +RTS -hT, que genera un perfil dinámico que puede ayudar a localizar la fuga de espacio (también, el programa debe vincularse con rtsopts habilitados).

Si se requiere un análisis más detallado, el programa tiene que ser compilado con seguimiento permitido (-rtsops -prof -fprof-auto, en GHCs mayores, la opción -fprof-auto no estaba disponible, la opción -prof-auto-all es la correspondencia más cercana allí).

Luego lo ejecuta con varias opciones de perfil y mira los perfiles de montón generados.

Las dos causas más comunes de fugas de espacio son

  • demasiada pereza
  • demasiada rigurosidad

el tercer lugar es probablemente tomada por el intercambio no deseado, GHC hace poco eliminación de subexpresiones comunes , pero ocasionalmente comparte largas listas incluso donde no se desea.

Para encontrar la causa de una fuga, sé de nuevo que no existen reglas estrictas, y de vez en cuando, se puede corregir una fuga agregando rigor en un lugar o agregando pereza en otro.

¿Cómo puedo ver si hay un problema con demasiada pereza? Siempre puedo verificar el perfil del montón, pero quiero saber cuáles son las causas generales, los ejemplos y los patrones en los que la pereza duele.

En general, la pereza es buscado donde los resultados pueden ser construidos de forma incremental, y no deseados en ninguna parte del resultado puede ser entregada antes de la transformación es completa, al igual que en los pliegues hacia la izquierda o en general en funciones de cola recursiva.

+0

Gracias por una muy buena respuesta. – Satvik

3

Recomiendo leer la documentación de GHC en Pragmas y Rewrite Rules, ya que abordan muchas de sus preguntas sobre SPECIALIZE y REGLAS.

referirse brevemente a sus preguntas:

  • SPECIALISE se utiliza para forzar al compilador para construir una versión especializada de una función polimórfica de un tipo particular. La ventaja es que aplicar la función en ese caso ya no requerirá el diccionario. La desventaja es que aumentará el tamaño de su programa. La especialización es particularmente valiosa para las funciones llamadas "bucles internos", y es esencialmente inútil para las llamadas funciones de alto nivel poco frecuentes. Consulte GHC documentation para conocer las interacciones con INLINE.

  • RULES le permite especificar reglas de reescritura que sabe que son válidas pero el compilador no pudo inferir por sí mismo. El ejemplo común es {-# RULES "mapfusion" forall f g xs. map f (map g xs) = map (f.g) xs #-}, que le dice a GHC cómo fusionar map. Puede ser complicado hacer que GHC use las reglas debido a la interferencia con INLINE. 7.19.3 toca cómo evitar conflictos y también cómo forzar a GHC a usar una regla incluso cuando normalmente la evitaría.

  • Los argumentos estrictos son más importantes para algo así como un acumulador en una función recursiva de cola. Usted sabe que el valor finalmente se calculará completamente, y acumular una pila de cierres para retrasar el cálculo derrota por completo el propósito. La rigurosidad forzada debe evitarse naturalmente cada vez que la función se puede aplicar a un valor que debe procesarse de forma perezosa, como una lista infinita. En general, la mejor idea es, al principio, forzar rigor solo donde obviamente es útil (como acumuladores), y luego agregar más tarde solo cuando el perfil demuestre que es necesario.

  • Mi experiencia ha sido que la mayoría de las fugas de espacio show-stopping vinieron de acumuladores perezosos y valores perezosos no evaluadas en grandes estructuras de datos, aunque estoy seguro de que esto es específico para el tipo de programas que escribe el usuario. Usar estructuras de datos no compartidas siempre que sea posible soluciona muchos de los problemas.

  • Fuera de los casos en que la pereza provoca fugas de espacio, la situación principal en la que debe evitarse es en IO. El recurso de procesamiento perezoso aumenta de forma inherente la cantidad de tiempo del reloj de pared en que se necesita el recurso. Esto puede ser malo para el rendimiento del caché, y obviamente es malo si algo más quiere derechos exclusivos para usar el mismo recurso.

Cuestiones relacionadas