1
- {-# LANGUAGE ScopedTypeVariables, QuasiQuotes #-}
1
+ {-# LANGUAGE ScopedTypeVariables, QuasiQuotes, TupleSections, NumericUnderscores #-}
2
2
3
3
module QuickBench (
4
4
defaultMain
5
5
)
6
6
where
7
7
8
- import Control.Exception
8
+ import Control.Exception hiding ( handle )
9
9
import Control.Monad
10
- import Data.List
10
+ import Data.Functor
11
+ import Data.List hiding (group )
11
12
import Data.List.Split (splitOn )
12
13
import Data.Maybe
13
14
import Data.Time.Clock
@@ -22,6 +23,7 @@ import System.IO
22
23
import System.Process
23
24
import Text.Show.Pretty
24
25
import Text.Printf
26
+ import Text.Read
25
27
import Text.Tabular
26
28
import qualified Text.Tabular.AsciiArt as TA
27
29
@@ -45,6 +47,7 @@ Options:
45
47
-n, --iterations=N run each test this many times [default: 1]
46
48
-N, --cycles=N run the whole suite this many times [default: 1]
47
49
-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)
48
51
-v, --verbose show commands being run
49
52
-V, --more-verbose show command output
50
53
--debug show debug output for this program
@@ -67,6 +70,7 @@ data Opts = Opts {
67
70
,iterations :: Int
68
71
,cycles :: Int
69
72
,precision :: Int
73
+ ,maxBytesUsed :: Bool
70
74
,verbose :: Bool
71
75
,moreVerbose :: Bool
72
76
,debug :: Bool
@@ -97,6 +101,7 @@ getOpts = do
97
101
,precision = precision'
98
102
,verbose = flag " verbose"
99
103
,moreVerbose = flag " more-verbose"
104
+ ,maxBytesUsed= flag " max-bytes-used"
100
105
,debug = flag " debug"
101
106
,help = flag " help"
102
107
,clicmds = args
@@ -170,14 +175,29 @@ getCurrentZonedTime = do
170
175
tz <- getCurrentTimeZone
171
176
return $ utcToZonedTime tz t
172
177
173
- runTestWithExes :: Opts -> [String ] -> String -> IO [[Float ]]
178
+ runTestWithExes :: Opts -> [String ] -> String -> IO [[( Float , Maybe Int ) ]]
174
179
runTestWithExes opts exes cmd = mapM (runTestWithExe opts cmd) exes
175
180
176
- runTestWithExe :: Opts -> String -> String -> IO [Float ]
181
+ runTestWithExe :: Opts -> String -> String -> IO [( Float , Maybe Int ) ]
177
182
runTestWithExe opts cmd exe = mapM (runTestOnce opts cmd exe) [1 .. iterations opts]
178
183
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
181
201
let (cmd',exe',args) = replaceExecutable exe cmd
182
202
dbg opts $ printf " replaceExecutable: %s -> %s\n " (show (cmd,exe)) (show (cmd',exe',args))
183
203
outv opts (show iteration ++ " : " ++ cmd' ++ " \n " )
@@ -210,7 +230,7 @@ readProcessWithExitCode' exe args inp =
210
230
readProcessWithExitCode exe args inp
211
231
`catch` \ (e :: IOException ) -> return (ExitFailure 1 , " " , show e)
212
232
213
- printSummary :: Opts -> [String ] -> [String ] -> Int -> [[[Float ]]] -> IO ()
233
+ printSummary :: Opts -> [String ] -> [String ] -> Int -> [[[( Float , Maybe Int ) ]]] -> IO ()
214
234
printSummary opts cmds exes cyc results = do
215
235
out opts $ printf " \n Best times%s:\n " (if cycles opts > 1 then " " ++ show cyc else " " )
216
236
let t = maketable opts cmds' exes results
@@ -225,18 +245,63 @@ printSummary opts cmds exes cyc results = do
225
245
[e] -> [c | (c,_,_) <- map (replaceExecutable e) cmds]
226
246
_ -> map (unwords . drop 1 . words ) cmds
227
247
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
+ ]
230
261
where
231
- rowhdrs = Group NoLine $ map Header $ padright rownames
232
- colhdrs = Group SingleLine $ map Header colnames
233
- rows = map (map (showtime opts . minimum )) results
234
262
padright ss = map (printf (printf " %%-%ds" w)) ss
235
263
where w = maximum $ map length ss
236
264
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
+
237
285
showtime :: Opts -> (Float -> String )
238
286
showtime opts = printf $ " %." ++ show (precision opts) ++ " f"
239
287
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
+
240
305
istest :: String -> Bool
241
306
istest s = not (null s' || (" #" `isPrefixOf` s')) where s' = clean s
242
307
0 commit comments