Huella de memoria de los tipos de datos Haskell

124

¿Cómo puedo encontrar la cantidad real de memoria requerida para almacenar un valor de algún tipo de datos en Haskell (principalmente con GHC)? ¿Es posible evaluarlo en tiempo de ejecución (por ejemplo, en GHCi) o es posible estimar los requisitos de memoria de un tipo de datos compuesto a partir de sus componentes?

En general, si los requisitos de memoria de tipos ay bson conocidos, lo que es la sobrecarga de memoria de tipos de datos algebraicos tales como:

data Uno = Uno a
data Due = Due a b

Por ejemplo, ¿cuántos bytes en memoria ocupan estos valores?

1 :: Int8
1 :: Integer
2^100 :: Integer
\x -> x + 1
(1 :: Int8, 2 :: Int8)
[1] :: [Int8]
Just (1 :: Int8)
Nothing

Entiendo que la asignación de memoria real es mayor debido a la recolección de basura retrasada. Puede ser significativamente diferente debido a la evaluación diferida (y el tamaño del thunk no está relacionado con el tamaño del valor). La pregunta es, dado un tipo de datos, ¿cuánta memoria toma su valor cuando se evalúa por completo?

Descubrí que hay una :set +sopción en GHCi para ver las estadísticas de memoria, pero no está claro cómo estimar la huella de memoria de un solo valor.

sastanin
fuente

Respuestas:

156

(Lo siguiente se aplica a GHC, otros compiladores pueden usar diferentes convenciones de almacenamiento)

Regla general: un constructor cuesta una palabra para un encabezado y una palabra para cada campo . Excepción: un constructor sin campos (como Nothingo True) no ocupa espacio, porque GHC crea una sola instancia de estos constructores y la comparte entre todos los usos.

Una palabra tiene 4 bytes en una máquina de 32 bits y 8 bytes en una máquina de 64 bits.

Entonces eg

data Uno = Uno a
data Due = Due a b

an Unotoma 2 palabras y a Duetoma 3.

El Inttipo se define como

data Int = I# Int#

ahora, Int#toma una palabra, entonces Inttoma 2 en total. La mayoría de los tipos sin caja toman una palabra, las excepciones son Int64#, Word64#y Double#(en una máquina de 32 bits) que toman 2. GHC en realidad tiene un caché de pequeños valores de tipo Inty Char, por lo tanto, en muchos casos, estos no ocupan espacio de almacenamiento dinámico. A Stringsolo requiere espacio para las celdas de la lista, a menos que use Chars> 255.

An Int8tiene una representación idéntica a Int. Integerse define así:

data Integer
  = S# Int#                            -- small integers
  | J# Int# ByteArray#                 -- large integers

entonces un pequeño Integer( S#) toma 2 palabras, pero un entero grande toma una cantidad variable de espacio dependiendo de su valor. A ByteArray#toma 2 palabras (encabezado + tamaño) más espacio para la matriz en sí.

Tenga en cuenta que un constructor definido con newtypees gratuito . newtypees puramente una idea de tiempo de compilación, y no ocupa espacio y no cuesta instrucciones en tiempo de ejecución.

Más detalles en The Layout of Heap Objects en el GHC Commentary .

Simon Marlow
fuente
1
Gracias Simon. Esto es exactamente lo que quería saber.
sastanin
2
¿No es el encabezado dos palabras? ¿Uno para la etiqueta y otro para el puntero de reenvío para usar durante la GC o la evaluación? Entonces, ¿eso no agregaría una palabra a su total?
Edward KMETT
55
@Edward: los thunks se sobrescriben mediante indirecciones (que luego son eliminadas por el GC), pero esas son solo 2 palabras, y se garantiza que cada objeto de montón tendrá al menos dos 2 palabras de tamaño. Sin ninguna característica de depuración o de perfil activada, el encabezado realmente es solo una palabra. En GHC, es decir, otras implementaciones pueden hacer las cosas de manera diferente.
nominolo
3
nominolo: sí, pero de Closure.h: / * Un thunk tiene una palabra de relleno para tomar el valor actualizado. Esto es para que la actualización no sobrescriba la carga útil, por lo que podemos evitar tener que bloquear el procesador durante la entrada y la actualización. Nota: esto no se aplica a THUNK_STATICs, que no tienen carga útil. Nota: dejamos esta palabra de relleno de todas las formas, en lugar de solo SMP, para que no tengamos que volver a compilar todas nuestras bibliotecas para SMP. * / La carga útil no se sobrescribe durante una indirección. La indirección se escribe en una ubicación separada en el encabezado.
Edward KMETT
66
Sí, pero tenga en cuenta que esto es solo para thunks . No se aplica a los constructores. De todos modos, estimar el tamaño de un thunk es un poco difícil: debe contar las variables libres.
nominolo
4

El paquete ghc-datasize proporciona la función recursiveSize para calcular el tamaño de un objeto GHC. Sin embargo...

Se realiza una recolección de basura antes de que se calcule el tamaño, porque el recolector de basura dificultaría las caminatas del montón.

... ¡así que no sería práctico llamar a esto a menudo!

Consulte también ¿Cómo encontrar las representaciones de memoria de tipos de datos de GHC? y ¿Cómo puedo determinar el tamaño de un tipo en Haskell? .

mhwombat
fuente