tipos sirve simbolos que para opciones mundo hola hacer funciones ejemplos datos como debugging haskell infinite-loop ghc ghci

debugging - sirve - Depuración de bucles infinitos en programas Haskell con GHCi



string en haskell (6)

Por primera vez me he encontrado con un bucle infinito en un programa de Haskell que estoy escribiendo. Lo he reducido a una sección bastante específica del código, pero parece que no puedo identificar exactamente dónde tengo una definición recursiva que no termina. Estoy vagamente familiarizado con: trace y: history en GHCi, pero el problema es que algunas ramas de mi código implican un poco de modificaciones recursivas de un Data.Map.Map en el sentido de que el mapa x se obtiene adjust algo en el mapa x'' basado en valores en otro mapa dependiendo de x'' . Los detalles no importan aquí, pero como probablemente pueda decir, si esto sucede de manera recursiva entrelazada, mi historial de llamadas se atasca por completo en todas las diversas comparaciones involucradas en las lookup mapas, adjust e iones de insert .

¿Alguien puede recomendar una forma más eficiente de ubicar bucles infinitos? Por ejemplo, ayudaría mucho a restringir el historial de llamadas a las llamadas desde un único archivo fuente.


¿No puede usar: atrás y: adelante para visitar su historial / rastreo y averiguar la evolución de sus mapas entre las llamadas?

Debería poder detectar el patrón que conduce al bucle recursivo.

- Si es demasiado complicado, es posible que hayas llegado al punto en el que escribes un código demasiado inteligente para que lo puedas depurar (o tal vez sea demasiado complicado y deberías refactorizarlo ^^) -


Asegúrese de haber utilizado el depurador GHCi en toda su extensión, incluido el ajuste -fbreak-on-exception (útil si está obteniendo <<loop>> , ¿verdad?) Y asegúrese de haber probado el consejo de Stephen de usar Las advertencias de GHC.

Si estos fallan (el depurador de GHCi realmente no debería ''fallar'', es solo una cuestión de interpretar los datos), entonces intente ejecutar HPC en el caso del bucle para que pueda ver visualmente las ramas y los valores que no se están evaluando, si es un bucle entonces, algo que se debería hacer probablemente ni siquiera se esté evaluando y se mostrará en el código HTML marcado.


Como dice ShiDoiSi, la solución "a ojo" suele ser la forma más exitosa.

Si está enlazando a diferentes variables con nombres similares x, x '', etc. en las mismas funciones, podría intentar habilitar las advertencias en la parte superior de su archivo:

{-# OPTIONS -Wall #-}

Si se trata de un problema en el que está ligado a lo incorrecto y está haciendo una recursión fuera de control, esto podría ayudarlo a detectarlo, por ejemplo, indicando un uso inesperado de la sombra .


Estoy en medio de una larga sesión de depuración para encontrar la causa de un bucle infinito. Me estoy acercando mucho, y esto es lo que más me ayudó. Supongamos que su bucle es causado por algo como esto:

... x1 = f1 x2 y x2 = f2 z x3 x3 = f3 y x1 ...

Entonces x1 depende de x2, que depende de x3, que depende de x1. ¡MALO!

Funciones de rastreo de rociado en las definiciones de f1, f2, f3. Algo como:

f1 x y | trace ("f1: ") False = undefined f1 x y = ... -- definition of f1 f2 x y | trace ("f2: ") False = undefined f2 x y = ... -- definition of f2 -- same for f3

Ejecute su programa para ver cuál de estas funciones se llama. La salida podría ser algo así como

f3: f2: f1: <<loop>>

Luego comienza a mostrar algunas variables en las funciones de rastreo. Por ejemplo, si cambia la traza de f2 a

f2 x y | trace ("f2: x: " ++ show x) False = undefined

entonces la salida se verá algo como:

f3: f2: x: x_value f1: <<loop>>

Pero si luego cambias la traza de f2 a

f2 x y | trace ("f2: x: " show x ++ " y: " ++ show y) False = undefined

Entonces la salida será

f3: <<loop>>

porque el segundo argumento de f2 no ​​puede ser evaluado debido a la dependencia circular.

Así que ahora sabes que una de las funciones en el bucle infinito es f2 y que su segundo argumento (pero no el primero) tiene una dependencia circular.

Feliz depuración!


Habiendo hackeado algunas líneas de Haskell, debo decir que nunca tuve mucha suerte con la depuración. Siempre fue la revisión exhaustiva del código y la refactorización lo que finalmente me ayudó a localizar el problema. Pero concedido, eso es solo anecdótico.


Me sorprende que nadie haya mencionado la respuesta generalizada que reciben todos los problemas de rendimiento de Haskell (el tiempo de ejecución infinito es un ejemplo bastante extremo de un "problema de rendimiento"): ¡perfilar!

Simplemente pude identificar rápidamente un bucle infinito utilizando perfiles. Para completar, compile con -prof -fprof-auto , luego ejecute el programa durante el tiempo suficiente para que la función ofensiva sea evidente en las estadísticas de perfiles. Por ejemplo, esperaba que mi programa se completara en <1 segundo, así que dejé que el generador de perfiles funcionara durante unos 30 segundos y luego eliminé mi programa con Ctrl + C. (Nota: la creación de perfiles guarda resultados incrementales, por lo que aún puede obtener datos significativos incluso si cancela el programa antes de que se ejecute hasta su finalización. EDITAR : Excepto cuando no lo haga ) .

En el archivo .prof, encontré el siguiente bloque:

individual inherited COST CENTRE MODULE no. entries %time %alloc %time %alloc ... primroot./ Zq 764 3 10.3 13.8 99.5 100.0 primroot.isGen Zq 1080 50116042 5.3 6.9 89.2 86.2 primroot.isGen./ Zq 1087 50116042 43.4 51.7 83.8 79.3 fromInteger ZqBasic 1088 0 40.4 27.6 40.4 27.6

Así que hay 50 millones de entradas a primroot.isGen , y la siguiente función más llamada solo tuvo 1024 llamadas. Además, el 99,5% del tiempo de ejecución se gastó en la computación de primroot , lo que parece altamente sospechoso. Revisé esa función y rápidamente encontré el error, un simple error tipográfico en mi caso: (`div` foo) lugar de (div foo) .

Creo que vale la pena tener en cuenta que las advertencias de GHC no habrían detectado este problema, ni -fbreak-on-exceptions . El código base es enorme; intentar localizar el problema insertando sentencias de depuración (de cualquier tipo) no me llevó a ningún lado. Tampoco tuve éxito usando el depurador GHCi porque el historial era esencialmente inexistente, y HPC no reveló nada útil.