hsbencher-1.20: HSBencher/Internal/BenchSpace.hs
{-# LANGUAGE NamedFieldPuns #-}
-- | Code for dealing with the `BenchSpace` datatype.
module HSBencher.Internal.BenchSpace
(BenchSpace(..), enumerateBenchSpace,
benchSpaceSize,
filterBenchmarks, filterBenchmark,
disjunctiveNF)
where
import Data.Maybe
import qualified Data.Set as S
import Data.List
import HSBencher.Types
-- | The size of a configuration space. This is equal to the length of
-- the result returned by `enumerateBenchSpace`, but is quicker to
-- compute.
benchSpaceSize :: BenchSpace a -> Int
benchSpaceSize Set{} = 1
benchSpaceSize (And x) = product $ map benchSpaceSize x
benchSpaceSize (Or x) = sum $ map benchSpaceSize x
-- | Exhaustively compute all configurations described by a benchmark configuration space.
enumerateBenchSpace :: BenchSpace a -> [ [(a,ParamSetting)] ]
enumerateBenchSpace bs =
case bs of
Set m p -> [ [(m,p)] ]
Or ls -> concatMap enumerateBenchSpace ls
And ls -> loop ls
where
loop [] = [ [] ] -- And [] => one config
loop [lst] = enumerateBenchSpace lst
loop (hd:tl) =
let confs = enumerateBenchSpace hd in
[ c++r | c <- confs
, r <- loop tl ]
-- | Filter down a list of benchmarks (and their configuration spaces)
-- to only those that have ALL of the pattern arguments occurring
-- somewhere in their printed representation.
--
-- This completely removes any benchmark with an empty configuration
-- space (`Or []`).
filterBenchmarks :: [String] -- ^ Patterns which must all be matched.
-> [Benchmark DefaultParamMeaning] -> [Benchmark DefaultParamMeaning]
filterBenchmarks [] = id -- Don't convert to disjunctive normal form!
filterBenchmarks patterns = mapMaybe fn
where
fn b = case filterBenchmark patterns b of
Benchmark{configs} | configs == Or[] -> Nothing
| otherwise -> Just b
-- | Filter down the config space of a benchmark, to only those
-- configurations that have a match for ALL of the pattern arguments
-- occurring somewhere inside them.
--
-- A note on semantics:
--
-- A benchmark (with its config space) implies a STREAM of concrete
-- benchmark instances.
--
-- Each pattern filters out a subset of these instances (either by
-- matching a varying field like `RuntimeEnv` or a static field like
-- `progname`). This function conjoins all the patterns and thus
-- returns a benchmark that iterates over the INTERSECTION of those
-- subsets implied by each pattern, respectively.
--
filterBenchmark :: [String] -- ^ Patterns which must all be matched.
-> Benchmark DefaultParamMeaning -> Benchmark DefaultParamMeaning
filterBenchmark patterns orig@Benchmark{target,cmdargs,progname,configs} =
let unmet = [ pat | pat <- patterns
, not (isInfixOf pat target ||
isInfixOf pat (fromMaybe "" progname) ||
any (isInfixOf pat) cmdargs) ]
newcfgs = filtConfigs unmet configs
in orig { configs = newcfgs }
-- Returns Or [] for an empty config space.
filtConfigs :: Show a => [String] -> BenchSpace a -> BenchSpace a
filtConfigs pats bs =
let Or ls = disjunctiveNF bs
in Or [ And as | And as <- ls, andMatch pats as ]
-- Does a conjoined sequence of Set clauses match ALL patterns?
andMatch :: Show a => [String] -> [BenchSpace a] -> Bool
andMatch pats0 ls = null (f pats0 ls)
where
f [] _ = []
f pats [] = pats
f pats (x@Set{} : rst) = let pats' = g pats x
in f pats' rst
g [] Set{} = []
g (hd:pats) (Set ls1 ls2) =
if isInfixOf hd (show ls1) || isInfixOf hd (show ls2)
then g pats (Set ls1 ls2)
else hd : g pats (Set ls1 ls2)
-- | Convert to disjunctive normal form. This can be an exponential
-- increase in the size of the value.
disjunctiveNF :: BenchSpace a -> BenchSpace a
disjunctiveNF = Or . map And . loop
where
loop bs =
case bs of
Set _ _ -> [[bs]]
And [] -> [[]]
And (h:t) -> [ x++y | x <- loop h
, y <- loop (And t) ]
Or ls -> concatMap loop ls
addAnd :: BenchSpace meaning -> BenchSpace meaning -> BenchSpace meaning
addAnd (Or []) _ = Or []
addAnd x (And ls) = And (x:ls)
addAnd x y = And [x,y]
addOr :: BenchSpace t -> BenchSpace t -> BenchSpace t
addOr (Or []) rst = rst
addOr x (Or ls) = Or (x:ls)
addOr x rst = Or [x,rst]
mkOr :: [BenchSpace meaning] -> BenchSpace meaning
mkOr [] = Or []
mkOr (x : tl) = addOr x (mkOr tl)
intersections :: Ord a => [S.Set a] -> S.Set a
intersections [] = error "No set intersection of the empty list"
intersections [s] = s
intersections (s1:sets) = S.intersection s1 (intersections sets)
bp1 :: BenchSpace DefaultParamMeaning
bp1 = (And [Set (Variant "Reduce") (RuntimeArg "Reduce"),
Set NoMeaning (RuntimeArg "r6") ])
t1 = filtConfigs ["Reduce", "r6"] bp1
t2 = filtConfigs ["Reduce", "r6"] (Or [ bp1, Set NoMeaning (RuntimeEnv "FOO" "r6") ])