Skip to content

Commit cc70cf8

Browse files
committed
feat: --max-bytes-used (#2)
+----------------------------------------------++---------------------------++-----------------------------+ | || Time (s) || Max bytes used | +==============================================++===========================++=============================+ | || hledger-1.40 hledger-1.41 || hledger-1.40 hledger-1.41 | +==============================================++===========================++=============================+ | -f examples/1ktxns-1kaccts.journal balance || 0.21 0.21 || 3.84M 4.28M | | -f examples/2ktxns-1kaccts.journal balance || 0.35 0.33 || 7.43M 6.39M | | -f examples/3ktxns-1kaccts.journal balance || 0.49 0.49 || 10.48M 11.59M | | -f examples/4ktxns-1kaccts.journal balance || 0.53 0.36 || 14.61M 11.49M | | -f examples/5ktxns-1kaccts.journal balance || 0.47 0.41 || 18.32M 15.52M | | -f examples/6ktxns-1kaccts.journal balance || 0.47 0.49 || 21.72M 21.60M | | -f examples/7ktxns-1kaccts.journal balance || 0.55 0.57 || 22.35M 25.17M | | -f examples/8ktxns-1kaccts.journal balance || 0.61 0.64 || 22.28M 24.02M | | -f examples/9ktxns-1kaccts.journal balance || 0.70 0.67 || 31.41M 24.03M | | -f examples/10ktxns-1kaccts.journal balance || 0.77 0.78 || 36.00M 35.56M | | -f examples/20ktxns-1kaccts.journal balance || 1.52 1.55 || 72.62M 72.62M | | -f examples/30ktxns-1kaccts.journal balance || 2.19 2.29 || 85.87M 96.99M | | -f examples/40ktxns-1kaccts.journal balance || 2.91 3.04 || 120.17M 130.21M | | -f examples/50ktxns-1kaccts.journal balance || 3.60 3.62 || 129.01M 140.33M | | -f examples/60ktxns-1kaccts.journal balance || 4.28 4.44 || 162.47M 175.05M | | -f examples/70ktxns-1kaccts.journal balance || 4.99 5.07 || 195.87M 207.53M | | -f examples/80ktxns-1kaccts.journal balance || 5.60 5.76 || 211.37M 219.93M | | -f examples/90ktxns-1kaccts.journal balance || 6.34 6.49 || 241.11M 250.92M | | -f examples/100ktxns-1kaccts.journal balance || 6.90 7.01 || 255.24M 264.18M | +----------------------------------------------++---------------------------++-----------------------------+
1 parent ca3c22e commit cc70cf8

File tree

3 files changed

+82
-13
lines changed

3 files changed

+82
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ Options:
169169
-n, --iterations=N run each test this many times [default: 1]
170170
-N, --cycles=N run the whole suite this many times [default: 1]
171171
-p, --precision=N show times with this many decimal places [default: 2]
172+
-m, --max-bytes-used measure max residency (Haskell programs compiled with `-rtsopts` only)
172173
-v, --verbose show commands being run
173174
-V, --more-verbose show command output
174175
--debug show debug output for this program

quickbench.1.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ With -w, commands' first words are replaced with a new executable
4444
`-p, --precision=N`
4545
: show times with this many decimal places [default: 2]
4646

47+
`-m, --max-bytes-used`
48+
: measure max residency (Haskell programs compiled with `-rtsopts` only)
49+
4750
`-v, --verbose`
4851
: show commands being run
4952

src/QuickBench.hs

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
{-# LANGUAGE ScopedTypeVariables, QuasiQuotes #-}
1+
{-# LANGUAGE ScopedTypeVariables, QuasiQuotes, TupleSections, NumericUnderscores #-}
22

33
module QuickBench (
44
defaultMain
55
)
66
where
77

8-
import Control.Exception
8+
import Control.Exception hiding (handle)
99
import Control.Monad
10-
import Data.List
10+
import Data.Functor
11+
import Data.List hiding (group)
1112
import Data.List.Split (splitOn)
1213
import Data.Maybe
1314
import Data.Time.Clock
@@ -22,6 +23,7 @@ import System.IO
2223
import System.Process
2324
import Text.Show.Pretty
2425
import Text.Printf
26+
import Text.Read
2527
import Text.Tabular
2628
import qualified Text.Tabular.AsciiArt as TA
2729

@@ -45,6 +47,7 @@ Options:
4547
-n, --iterations=N run each test this many times [default: 1]
4648
-N, --cycles=N run the whole suite this many times [default: 1]
4749
-p, --precision=N show times with this many decimal places [default: 2]
50+
-m, --max-bytes-used measure max residency (Haskell programs compiled with `-rtsopts` only)
4851
-v, --verbose show commands being run
4952
-V, --more-verbose show command output
5053
--debug show debug output for this program
@@ -67,6 +70,7 @@ data Opts = Opts {
6770
,iterations :: Int
6871
,cycles :: Int
6972
,precision :: Int
73+
,maxBytesUsed:: Bool
7074
,verbose :: Bool
7175
,moreVerbose :: Bool
7276
,debug :: Bool
@@ -97,6 +101,7 @@ getOpts = do
97101
,precision = precision'
98102
,verbose = flag "verbose"
99103
,moreVerbose = flag "more-verbose"
104+
,maxBytesUsed= flag "max-bytes-used"
100105
,debug = flag "debug"
101106
,help = flag "help"
102107
,clicmds = args
@@ -170,14 +175,29 @@ getCurrentZonedTime = do
170175
tz <- getCurrentTimeZone
171176
return $ utcToZonedTime tz t
172177

173-
runTestWithExes :: Opts -> [String] -> String -> IO [[Float]]
178+
runTestWithExes :: Opts -> [String] -> String -> IO [[(Float, Maybe Int)]]
174179
runTestWithExes opts exes cmd = mapM (runTestWithExe opts cmd) exes
175180

176-
runTestWithExe :: Opts -> String -> String -> IO [Float]
181+
runTestWithExe :: Opts -> String -> String -> IO [(Float, Maybe Int)]
177182
runTestWithExe opts cmd exe = mapM (runTestOnce opts cmd exe) [1..iterations opts]
178183

179-
runTestOnce :: Opts -> String -> String -> Int -> IO Float
180-
runTestOnce opts cmd exe iteration = do
184+
runTestOnce :: Opts -> String -> String -> Int -> IO (Float, Maybe Int)
185+
runTestOnce opts cmd exe iteration = if maxBytesUsed opts
186+
then runTimeAndResidencyTest opts cmd exe iteration
187+
else runTimeTest opts cmd exe iteration <&> (,Nothing)
188+
189+
runTimeAndResidencyTest :: Opts -> String -> String -> Int -> IO (Float, Maybe Int)
190+
runTimeAndResidencyTest opts cmd exe iteration = withTempFile $ \name handle -> do
191+
t <- runTimeTest opts (cmd ++ " +RTS --machine-readable -t" ++ name) exe iteration
192+
_ <- hGetLine handle -- skip first line
193+
stats <- hGetContents' handle
194+
return (t, readMaybe stats >>= findMaxBytesUsed)
195+
where
196+
findMaxBytesUsed :: [(String, String)] -> Maybe Int
197+
findMaxBytesUsed pairs = find ((== "max_bytes_used") . fst) pairs >>= readMaybe . snd
198+
199+
runTimeTest :: Opts -> String -> String -> Int -> IO Float
200+
runTimeTest opts cmd exe iteration = do
181201
let (cmd',exe',args) = replaceExecutable exe cmd
182202
dbg opts $ printf "replaceExecutable: %s -> %s\n" (show (cmd,exe)) (show (cmd',exe',args))
183203
outv opts (show iteration ++ ": " ++ cmd' ++ "\n")
@@ -210,7 +230,7 @@ readProcessWithExitCode' exe args inp =
210230
readProcessWithExitCode exe args inp
211231
`catch` \(e :: IOException) -> return (ExitFailure 1, "", show e)
212232

213-
printSummary :: Opts -> [String] -> [String] -> Int -> [[[Float]]] -> IO ()
233+
printSummary :: Opts -> [String] -> [String] -> Int -> [[[(Float, Maybe Int)]]] -> IO ()
214234
printSummary opts cmds exes cyc results = do
215235
out opts $ printf "\nBest times%s:\n" (if cycles opts > 1 then " "++show cyc else "")
216236
let t = maketable opts cmds' exes results
@@ -225,18 +245,63 @@ printSummary opts cmds exes cyc results = do
225245
[e] -> [c | (c,_,_) <- map (replaceExecutable e) cmds]
226246
_ -> map (unwords . drop 1 . words) cmds
227247

228-
maketable :: Opts -> [String] -> [String] -> [[[Float]]] -> Table String String String
229-
maketable opts rownames colnames results = Table rowhdrs colhdrs rows
248+
maketable :: Opts -> [String] -> [String] -> [[[(Float, Maybe Int)]]] -> Table String String String
249+
maketable opts rownames colnames results = Table rowhdrs grouphdrs (firstrow:rows)
250+
where
251+
rowhdrs = makeRowHeaders rownames
252+
grouphdrs = makeGroupHeaders opts colnames
253+
firstrow = colnames ++ colnames
254+
rows = map (makeRow opts) results
255+
256+
makeRowHeaders :: [String] -> Header String
257+
makeRowHeaders rownames = Group DoubleLine [
258+
Group NoLine [Header ""],
259+
Group NoLine $ map Header $ padright rownames
260+
]
230261
where
231-
rowhdrs = Group NoLine $ map Header $ padright rownames
232-
colhdrs = Group SingleLine $ map Header colnames
233-
rows = map (map (showtime opts . minimum)) results
234262
padright ss = map (printf (printf "%%-%ds" w)) ss
235263
where w = maximum $ map length ss
236264

265+
{-
266+
makeColumnHeaders :: Opts -> [String] -> Header String
267+
makeColumnHeaders opts colnames =
268+
Group DoubleLine . replicate (if maxBytesUsed opts then 2 else 1) . Group SingleLine $ map Header colnames
269+
-}
270+
271+
-- Workaround for https://github.com/bgamari/tabular/issues/4
272+
makeGroupHeaders :: Opts -> [String] -> Header String
273+
makeGroupHeaders opts colnames =
274+
Group DoubleLine $ map (Group NoLine . headers) groups
275+
where
276+
groups = if maxBytesUsed opts then ["Time (s)", "Max bytes used"] else ["Time (s)"]
277+
headers group = take (length colnames) . map Header $ group:repeat ""
278+
279+
makeRow :: Opts -> [[(Float, Maybe Int)]] -> [String]
280+
makeRow opts results = if maxBytesUsed opts then times ++ bytes else times
281+
where
282+
times = map (showtime opts . minimum . map fst) results
283+
bytes = map (showbytes opts . minimum . map (fromMaybe 0 . snd)) results
284+
237285
showtime :: Opts -> (Float -> String)
238286
showtime opts = printf $ "%." ++ show (precision opts) ++ "f"
239287

288+
showbytes :: Opts -> Int -> String
289+
showbytes opts n
290+
| abs n >= 1000_000_000 = printf ("%." ++ show (precision opts) ++ "fG") (fromIntegral n / 1000_0000_0000 :: Double)
291+
| abs n >= 1000_000 = printf ("%." ++ show (precision opts) ++ "fM") (fromIntegral n / 1000_0000 :: Double)
292+
| abs n >= 1000 = printf ("%." ++ show (precision opts) ++ "fK") (fromIntegral n / 1000 :: Double)
293+
| otherwise = show n
294+
295+
withTempFile :: (FilePath -> Handle -> IO a) -> IO a
296+
withTempFile action = do
297+
tmp_dir <- getTemporaryDirectory >>= canonicalizePath
298+
bracket
299+
(openTempFile tmp_dir "quickbench-")
300+
(\(name, handle) -> hClose handle >> ignoringIOErrors (removeFile name))
301+
(uncurry action)
302+
where
303+
ignoringIOErrors = void . (try :: IO a -> IO (Either IOException a))
304+
240305
istest :: String -> Bool
241306
istest s = not (null s' || ("#" `isPrefixOf` s')) where s' = clean s
242307

0 commit comments

Comments
 (0)