È possibile utilizzare force
da Control.DeepSeq
per valutare completamente una struttura di dati (e quindi della domanda e misurare il suo calcolo).
Un problema è che la forzatura di una struttura di dati di grandi dimensioni richiede un po 'di tempo prima che sia !
Questo perché un deepseq
(usato da force
) sarà a piedi lungo il vostro tipo di dati algebrica albero, visitando ogni nodo (ma non fare nulla con esso).
Quando si esegue un'operazione a basso costo su ciascun nodo, ad esempio map (*2) mylist
, e si tenta di misurare il tempo necessario, questo sovraccarico può diventare improvvisamente significativo, compromettendo le misurazioni.
import Control.DeepSeq
import Control.Exception (evaluate)
import Data.Time (diffUTCTime, getCurrentTime)
-- | Measures how long a computation takes, printing both the time and the
-- overhead of `force` to stdout. So it forces *twice*.
benchmarkForce :: NFData a => String -> IO a -> IO a
benchmarkForce msg action = do
before <- getCurrentTime
-- Force the first time to measure computation + forcing
result <- evaluate . force =<< action
after <- getCurrentTime
-- Force again to see how long forcing itself takes
_ <- evaluate . force $ result
afterAgain <- getCurrentTime
putStrLn $ msg ++ ": " ++ show (diffTimeMs before after) ++ " ms"
++ " (force time: " ++ show (diffTimeMs after afterAgain) ++ " ms)"
return result
where
-- Time difference `t2 - t1` in milliseconds
diffTimeMs t1 t2 = realToFrac (t2 `diffUTCTime` t1) * 1000.0 :: Double
La prima esecuzione evaluate . force
farà in modo che il vostro il suo valore action
e di ritorno vengono valutati del tutto.
Effettuando un secondo controllo force
sul risultato, è possibile misurare la quantità di sovraccarico aggiunta alla prima traversata.
Questo ovviamente va a scapito di due traversali; essere in grado di misurare quanto tempo uno spreco di deepseq
richieda di perdere quel tempo due volte.
Ecco un esempio per misurare alcune funzioni pure con quel:
main :: IO()
main = do
l <- benchmarkForce "create list" $
return [1..10000000 :: Integer]
_ <- benchmarkForce "double each list element" $
return $ map (*2) l
_ <- benchmarkForce "map id l" $
return $ map id l
return()
(Naturalmente funziona anche con funzioni IO.)
L'uscita:
create list: 1091.936 ms (force time: 71.33200000000001 ms)
double each list element: 1416.0569999999998 ms (force time: 96.808 ms)
map id l: 484.493 ms (force time: 67.232 ms)
Come possiamo vedere, il crea un overhead del 13% circa nel caso map id l
.
Il mio pacchetto "cronografo" può essere utilizzato per misurare i tempi di valutazione in presenza di pigrizia. Sfortunatamente il tempo di "forza" è incluso nella misurazione e, come lei indica, può essere significativo. –