2013-09-04 13 views
6

Ho un server Web scritto in Haskell che calcola alcuni dati in più passaggi.Haskell: Come eseguire il benchmarking di un calcolo in modo accurato con deepseq/force

Desidero misurare e visualizzare con precisione la durata di ogni azione.

In presenza di pigrizia, qual è un buon modo per farlo?


nota che "benchmarking" non è proprio la terminologia giusta dal momento voglio solo solo di misurare il tempo in un sistema di produzione e non assaggiare numerose piste. So che per quel caso posso usare criterion.

risposta

4

È 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.

+0

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. –