Mi sono divertito con le definizioni per comprendere meglio il modello di valutazione e ne ho scritte due per la lunghezza di un elenco.Perché una severa funzione di lunghezza ha prestazioni sensibilmente più veloci?
La definizione naive:
len :: [a] -> Int
len [] = 0
len (_:xs) = 1 + len xs
La rigida (e coda ricorsiva) definizione:
slen :: [a] -> Int -> Int
slen [] n = n
slen (_:xs) !n = slen xs (n+1)
len [1..10000000]
richiede circa 5-6 secondi per eseguire.
slen [1..10000000] 0
richiede circa 3-4 secondi per eseguire.
Sono curioso perché. Prima di controllare le performance, ero certo che avrebbero suonato allo stesso modo perché lo len
avrebbe dovuto avere solo un thunk in più per valutare al massimo. A scopo dimostrativo:
len [a,b,c,d]
= 1 + len [b,c,d]
= 1 + 1 + len [c,d]
= 1 + 1 + 1 + len [d]
= 1 + 1 + 1 + 1 + len []
= 1 + 1 + 1 + 1 + 0
= 4
E
slen [a,b,c,d] 0
= slen [b,c,d] 1
= slen [c,d] 2
= slen [d] 3
= slen [] 4
= 4
Ciò che rende slen
notevolmente più veloce?
P.S. Ho anche scritto una funzione lenta ricorsiva in coda (proprio come slen
ma pigra) come un tentativo di chiudere la ragione - forse perché era ricorsiva in coda - ma si è comportata all'incirca come nella definizione ingenua.
Il passaggio finale di 'len' non è O (1). È O (n) per sommare n numeri. 'slen' usa anche la memoria O (n) mentre' len' usa la memoria O (1). –
@DavidYoung Oh capisco! Sei il benvenuto a scriverlo come risposta. Potresti anche spiegare il consumo di memoria? (o solo un riferimento a dove posso capirlo ulteriormente). Molte grazie! – MasterMastic
Scusa, ho fatto all'indietro. 'len' usa la memoria O (n) e' slen' usa la memoria O (1). –