We now have a correctness base line: our QuickCheck tests pass. When we start tweaking performance, we can rerun the tests at any time to ensure that we haven’t inadvertently broken anything.
Our first step is to write a small test application that we can use for timing:
-- file: examples/WordTest.hs module Main where import Control.Parallel.Strategies (NFData(..)) import Control.Monad (forM_, mapM_) import qualified BloomFilter.Easy as B import qualified Data.ByteString.Char8 as BS import Data.Time.Clock (diffUTCTime, getCurrentTime) import System.Environment (getArgs) import System.Exit (exitFailure) timed :: (NFData a) => String -> IO a -> IO a timed desc act = do start <- getCurrentTime ret <- act end <- rnf ret `seq` getCurrentTime putStrLn $ show (diffUTCTime end start) ++ " to " ++ desc return ret instance NFData BS.ByteString where rnf _ = () instance NFData (B.Bloom a) where rnf filt = B.length filt `seq` ()
We borrow the
rnf function that we introduced in Separating Algorithm from Evaluation to develop a simple timing harness.
timed action ensures that a
value is evaluated to normal form in order to accurately capture the
cost of evaluating it.
The application creates a Bloom filter from the contents of a file, treating each line as an element to add to the filter:
-- file: examples/WordTest.hs main = do args <- getArgs let files | null args = ["/usr/share/dict/words"] | otherwise = args forM_ files $ \file -> do words <- timed "read words" ...