Footprint di memoria dei tipi di dati Haskell


124

Come posso trovare la quantità effettiva di memoria richiesta per memorizzare un valore di un certo tipo di dati in Haskell (principalmente con GHC)? È possibile valutarlo in fase di esecuzione (ad esempio in GHCi) o è possibile stimare i requisiti di memoria di un tipo di dati composto dai suoi componenti?

In generale, se i requisiti di memoria dei tipi ae bsono noti, qual è il sovraccarico di memoria dei tipi di dati algebrici come:

data Uno = Uno a
data Due = Due a b

Ad esempio, quanti byte in memoria occupano questi valori?

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

Capisco che l'allocazione della memoria effettiva è maggiore a causa della raccolta dei rifiuti ritardata. Può essere notevolmente diverso a causa della valutazione lenta (e la dimensione thunk non è correlata alla dimensione del valore). La domanda è, dato un tipo di dati, quanta memoria occupa il suo valore quando viene valutato completamente?

Ho scoperto che esiste :set +sun'opzione in GHCi per vedere le statistiche della memoria, ma non è chiaro come stimare l'impronta della memoria di un singolo valore.

Risposte:


156

(Quanto segue si applica a GHC, altri compilatori possono utilizzare convenzioni di archiviazione diverse)

Regola pratica: un costruttore costa una parola per un'intestazione e una parola per ogni campo . Eccezione: un costruttore senza campi (come Nothingo True) non occupa spazio, perché GHC crea una singola istanza di questi costruttori e la condivide tra tutti gli usi.

Una parola è di 4 byte su una macchina a 32 bit e 8 byte su una macchina a 64 bit.

Quindi ad es

data Uno = Uno a
data Due = Due a b

un Unorichiede 2 parole e a Due3.

Il Inttipo è definito come

data Int = I# Int#

ora, Int#richiede una parola, quindi ne Intrichiede 2 in totale. La maggior parte dei tipi unboxed prendono una parola, le eccezioni essendo Int64#, Word64#e Double#(su una macchina a 32-bit) che prendono 2. GHC in realtà ha una cache di piccoli valori di tipo Inte Char, così in molti casi questi non prendere lo spazio di heap a tutti. A Stringrichiede solo spazio per le celle dell'elenco, a meno che non si utilizzi Chars> 255.

An Int8ha una rappresentazione identica a Int. Integerè definito così:

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

quindi una piccola Integer( S#) richiede 2 parole, ma un intero grande occupa una quantità di spazio variabile a seconda del suo valore. A ByteArray#richiede 2 parole (intestazione + dimensione) più spazio per l'array stesso.

Notare che un costruttore definito con newtypeè gratuito . newtypeè un'idea puramente in fase di compilazione, non occupa spazio e non costa istruzioni in fase di esecuzione.

Maggiori dettagli in The Layout of Heap Objects nel GHC Commentary .


1
Grazie, Simon. Questo è esattamente quello che volevo sapere.
sastanin

2
L'intestazione non è composta da due parole? Uno per il tag e uno per il puntatore di inoltro da utilizzare durante GC o valutazione? Quindi non aggiungerebbe una parola al tuo totale?
Edward KMETT

5
@Edward: i thunk vengono sovrascritti da riferimenti indiretti (che vengono successivamente rimossi dal GC), ma queste sono solo 2 parole e ogni oggetto heap è garantito per avere almeno due 2 parole di dimensione. Senza alcuna funzionalità di profilazione o debug attivata, l'intestazione è davvero solo una parola. In GHC, cioè, altre implementazioni possono fare le cose in modo diverso.
nominolo

3
nominolo: sì, ma da Closure.h: / * Un thunk ha una parola di riempimento per assumere il valore aggiornato. In questo modo l'aggiornamento non sovrascrive il payload, così possiamo evitare di dover bloccare il thunk durante l'immissione e l'aggiornamento. Nota: questo non si applica ai THUNK_STATIC, che non hanno payload. Nota: lasciamo questa parola di riempimento in tutti i modi, piuttosto che solo SMP, in modo da non dover ricompilare tutte le nostre librerie per SMP. * / Il payload non viene sovrascritto durante un riferimento indiretto. Il riferimento indiretto viene scritto in una posizione separata nell'intestazione.
Edward KMETT

6
Sì, ma nota che questo è solo per thunk . Non si applica ai costruttori. Stimare la dimensione di un thunk è comunque un po 'difficile: devi contare le variabili libere.
nominolo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.