packages feed

rangeset-0.1.0.0: benchmarks/ContiguityBench.hs

{-# LANGUAGE BangPatterns, TypeApplications, ScopedTypeVariables, BlockArguments, AllowAmbiguousTypes  #-}
module Main where

import Gauge
import BenchmarkUtils

import Data.RangeSet (RangeSet)
import Data.EnumSet (EnumSet)
import Data.Set (Set)
import Data.Patricia (Patricia)
import qualified Data.RangeSet as RangeSet
import qualified Data.EnumSet as EnumSet
import qualified Data.Patricia as Patricia
import qualified Data.Set as Set

import Control.Monad
import Control.DeepSeq
import Data.List
import System.Random.Shuffle
import System.Random.Stateful

import qualified Data.List as List
import GHC.Real (Fractional, (%))
import Data.Ratio (denominator, numerator)

main :: IO ()
main = do
  --(ratios, bins) <- unzip <$> fillBins @Word
  (ratios, bins) <- unzip <$> fillBins' @Word
  condensedMain (Just "contiguity.csv") [
      contiguityBench ratios bins
    ]

-- This benchmark generates bunches of random data with a contiguity measure of 0.0 to 1.0
-- this is defined as $1 - m/n$ where $n$ is the number of elements in the set and $m$ the number
-- of ranges. For each of these random sets, each element is queried in turn, and the average
-- time is taken. This comparison is done between the rangeset and the data.set

contiguity :: RangeSet a -> Rational
contiguity s = 1 - fromIntegral (RangeSet.sizeRanges s) % fromIntegral (RangeSet.size s)

numContBins :: Int
numContBins = 20

binSize :: Int
binSize = 100 --for full bench

approxSetSize :: Int
approxSetSize = 1000

fillBins' :: forall a. (Ord a, Enum a, Bounded a, Num a, UniformRange a) => IO [(Rational, [(RangeSet a, Set a, EnumSet a, Patricia a, [a], [a])])]
fillBins' =
  do let granulation = 1 % fromIntegral numContBins
     let toRatio = (* granulation) . fromIntegral
     let cs = map toRatio [0..numContBins-1]

     forM cs $ \c -> do
        let xs = buildWithContiguity approxSetSize c
        xss <- (xs :) <$> replicateM (binSize-1) do
          xs' <- shuffleM xs
          -- allow the elements to shift so they may overlap partially, or even not at all with those in the original
          -- this is important for good distribution when testing union, intersection, difference but should be
          -- invariant for the other benchmarks
          shift <- uniformRM (0, fromIntegral approxSetSize) globalStdGen :: IO a
          return (map (+ shift) xs')
        return (c, map (\xs -> (RangeSet.fromList xs, Set.fromList xs, EnumSet.fromList xs, Patricia.fromList xs, xs, sort xs)) xss)

buildWithContiguity :: forall a. (Bounded a, Enum a) => Int -> Rational -> [a]
buildWithContiguity atLeast c = go [minBound @a .. maxBound @a] ranges
  where
    go xs []     = []
    go xs (n:ns) = let (r, xs') = splitAt n xs in r ++ go (tail xs') ns

    ranges = replicate numBig bigSize ++ replicate numSmall smallSize

    w = 1 / (1 - c)
    numElems = fromInteger $ numerator w
    numRangesUnscaled = fromInteger $ denominator w
    (smallSize, numBigUnscaled) = quotRem numElems numRangesUnscaled
    numSmallUnscaled = numRangesUnscaled - numBigUnscaled

    (q, r) = quotRem atLeast (smallSize * numSmallUnscaled + bigSize * numBigUnscaled)
    scale = q + (if r == 0 then 0 else 1)
    numBig = numBigUnscaled * scale
    numSmall = numSmallUnscaled * scale
    bigSize = smallSize + 1

contiguityBench :: forall a. (NFData a, Ord a, Enum a) => [Rational] -> [[(RangeSet a, Set a, EnumSet a, Patricia a, [a], [a])]] -> Benchmark
contiguityBench ratios bins = {-es `deepseq`-} env (return (map unzip6 bins)) $ \dat ->
    bgroup "contiguity" (concatMap (mkBench dat) (zip ratios [0..]))

  where
    --es = elems @a
    mkBench dat (ratio, i) = let ~(rs, ss, es, ps, xss, sxss) = dat !! i in [
        bench ("overhead from (" ++ show ratio ++ ")") $ whnf overheadFromList sxss,
        bench ("rangeset-from (" ++ show ratio ++ ")") $ whnf rangeSetFromList sxss,
        bench ("set-from (" ++ show ratio ++ ")") $ whnf setFromList sxss,
        bench ("set-opt-from (" ++ show ratio ++ ")") $ whnf enumSetFromList sxss,
        bench ("patricia-from (" ++ show ratio ++ ")") $ whnf patriciaFromList sxss,
        --bench ("overhead rangeset-all (" ++ show ratio ++ ")") $ whnf (overheadRangeSetAllMember es) rs,
        --bench ("overhead set-all (" ++ show ratio ++ ")") $ whnf (overheadSetAllMember es) ss,
        --bench ("rangeset-all (" ++ show ratio ++ ")") $ whnf (rangeSetAllMember es) rs,
        --bench ("set-all (" ++ show ratio ++ ")") $ whnf (setAllMember es) ss,
        bench ("overhead union (" ++ show ratio ++ ")") $ whnf overheadSetCrossSet rs,
        bench ("rangeset-union (" ++ show ratio ++ ")") $ whnf rangeSetUnion rs,
        bench ("set-union (" ++ show ratio ++ ")") $ whnf setUnion ss,
        bench ("set-opt-union (" ++ show ratio ++ ")") $ whnf enumSetUnion es,
        bench ("patricia-union (" ++ show ratio ++ ")") $ whnf patriciaUnion ps,
        bench ("overhead intersection (" ++ show ratio ++ ")") $ whnf overheadSetCrossSet rs,
        bench ("rangeset-intersection (" ++ show ratio ++ ")") $ whnf rangeSetIntersection rs,
        bench ("set-intersection (" ++ show ratio ++ ")") $ whnf setIntersection ss,
        bench ("set-opt-intersection (" ++ show ratio ++ ")") $ whnf enumSetIntersection es,
        bench ("patricia-intersection (" ++ show ratio ++ ")") $ whnf patriciaIntersection ps,
        bench ("overhead difference (" ++ show ratio ++ ")") $ whnf overheadSetCrossSet rs,
        bench ("rangeset-difference (" ++ show ratio ++ ")") $ whnf rangeSetDifference rs,
        bench ("set-difference (" ++ show ratio ++ ")") $ whnf setDifference ss,
        bench ("set-opt-difference (" ++ show ratio ++ ")") $ whnf enumSetDifference es,
        bench ("patricia-difference (" ++ show ratio ++ ")") $ whnf patriciaDifference ps,
        bench ("overhead mem (" ++ show ratio ++ ")") $ whnf (uncurry overheadMember) (xss, rs),
        bench ("rangeset-mem (" ++ show ratio ++ ")") $ whnf (uncurry rangeSetMember) (xss, rs),
        bench ("set-mem (" ++ show ratio ++ ")") $ whnf (uncurry setMember) (xss, ss),
        bench ("set-opt-mem (" ++ show ratio ++ ")") $ whnf (uncurry enumSetMember) (xss, es),
        bench ("patricia-mem (" ++ show ratio ++ ")") $ whnf (uncurry patriciaMember) (xss, ps),
        bench ("overhead ins (" ++ show ratio ++ ")") $ whnf overheadInsert xss,
        bench ("rangeset-ins (" ++ show ratio ++ ")") $ whnf rangeSetInsert xss,
        bench ("set-ins (" ++ show ratio ++ ")") $ whnf setInsert xss,
        bench ("set-opt-ins (" ++ show ratio ++ ")") $ whnf enumSetInsert xss,
        bench ("patricia-ins (" ++ show ratio ++ ")") $ whnf patriciaInsert xss,
        bench ("rangeset-ins-sorted (" ++ show ratio ++ ")") $ whnf rangeSetInsert sxss,
        bench ("set-ins-sorted (" ++ show ratio ++ ")") $ whnf setInsert sxss,
        bench ("set-opt-ins-sorted (" ++ show ratio ++ ")") $ whnf enumSetInsert sxss,
        bench ("patricia-ins-sorted (" ++ show ratio ++ ")") $ whnf patriciaInsert sxss,
        bench ("overhead del (" ++ show ratio ++ ")") $ whnf (uncurry overheadDelete) (xss, rs),
        bench ("rangeset-del (" ++ show ratio ++ ")") $ whnf (uncurry rangeSetDelete) (xss, rs),
        bench ("set-del (" ++ show ratio ++ ")") $ whnf (uncurry setDelete) (xss, ss),
        bench ("set-opt-del (" ++ show ratio ++ ")") $ whnf (uncurry enumSetDelete) (xss, es),
        bench ("patricia-del (" ++ show ratio ++ ")") $ whnf (uncurry patriciaDelete) (xss, ps),
        bench ("rangeset-del-sorted (" ++ show ratio ++ ")") $ whnf (uncurry rangeSetDelete) (sxss, rs),
        bench ("set-del-sorted (" ++ show ratio ++ ")") $ whnf (uncurry setDelete) (sxss, ss),
        bench ("set-opt-del-sorted (" ++ show ratio ++ ")") $ whnf (uncurry enumSetDelete) (sxss, es),
        bench ("patricia-del-sorted (" ++ show ratio ++ ")") $ whnf (uncurry patriciaDelete) (sxss, ps)
      ]

    overheadMember xss ys = nfList [False | (y, xs) <- zip ys xss, x <- xs]
    rangeSetMember xss rs = nfList [RangeSet.member x r | (r, xs) <- zip rs xss, x <- xs]
    setMember xss ss = nfList [Set.member x s | (s, xs) <- zip ss xss, x <- xs]
    patriciaMember xss ss = nfList [Patricia.member x s | (s, xs) <- zip ss xss, x <- xs]
    enumSetMember xss ss = nfList [EnumSet.member x s | (s, xs) <- zip ss xss, x <- xs]

    overheadInsert = nfList . map (foldr @[] @a (const id) ())
    rangeSetInsert = nfList . map (foldr @[] @a RangeSet.insert RangeSet.empty)
    setInsert = nfList . map (foldr @[] @a Set.insert Set.empty)
    patriciaInsert = nfList . map (foldr @[] @a Patricia.insert Patricia.empty)
    enumSetInsert = nfList . map (foldr @[] @a EnumSet.insert EnumSet.empty)

    overheadDelete xss ys = nfList $ zipWith (foldr (const id)) ys xss
    rangeSetDelete xss rs = nfList $ zipWith (foldr RangeSet.delete) rs xss
    setDelete xss ss = nfList $ zipWith (foldr Set.delete) ss xss
    patriciaDelete xss rs = nfList $ zipWith (foldr Patricia.delete) rs xss
    enumSetDelete xss ss = nfList $ zipWith (foldr EnumSet.delete) ss xss

    overheadSetCrossSet xs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ const <$> xs' <*> xs'

    rangeSetUnion rs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ RangeSet.union <$> rs' <*> rs'
    setUnion ss' = {-let ss' = take (binSize `div` 2) ss in -}nfList $ Set.union <$> ss' <*> ss'
    patriciaUnion rs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ Patricia.union <$> rs' <*> rs'
    enumSetUnion ss' = {-let ss' = take (binSize `div` 2) ss in -}nfList $ EnumSet.union <$> ss' <*> ss'

    rangeSetIntersection rs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ RangeSet.intersection <$> rs' <*> rs'
    setIntersection ss' = {-let ss' = take (binSize `div` 2) ss in -}nfList $ Set.intersection <$> ss' <*> ss'
    patriciaIntersection rs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ Patricia.intersection <$> rs' <*> rs'
    enumSetIntersection ss' = {-let ss' = take (binSize `div` 2) ss in -}nfList $ EnumSet.intersection <$> ss' <*> ss'

    rangeSetDifference rs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ RangeSet.difference <$> rs' <*> rs'
    setDifference ss' = {-let ss' = take (binSize `div` 2) ss in -}nfList $ Set.difference <$> ss' <*> ss'
    patriciaDifference rs' = {-let rs' = take (binSize `div` 2) rs in -}nfList $ Patricia.difference <$> rs' <*> rs'
    enumSetDifference ss' = {-let ss' = take (binSize `div` 2) ss in -}nfList $ EnumSet.difference <$> ss' <*> ss'

    overheadFromList = nfList . map (const ())
    rangeSetFromList = nfList . map RangeSet.fromList
    setFromList = nfList . map Set.fromList
    patriciaFromList = nfList . map Patricia.fromList
    enumSetFromList = nfList . map EnumSet.fromList

{-
maxElem :: Enum a => a
maxElem = toEnum (approxSetSize - 1)

minElem :: Enum a => a
minElem = toEnum 0

elems :: forall a. Enum a => [a]
elems = [minElem @a .. maxElem @a]

fillBins :: forall a. (Ord a, Enum a) => IO [(Rational, [(RangeSet a, Set a, [a], [a])])]
fillBins = do
  bins <- newArray (0, numContBins-1) [] :: IO (IOArray Int [(RangeSet a, [a])])
  let granulation = 1 % fromIntegral numContBins
  let toRatio = (* granulation) . fromIntegral
  let idxs = map toRatio [0..numContBins-1]
  --print idxs

  whileS do
    shuffled <- shuffleM (elems @a)
    let sets = scanr (\x -> bimap (RangeSet.insert x) (x:)) (RangeSet.empty, []) shuffled
    forM_ (init sets) $ \set -> do
      let c = contiguity (fst set)
      let idx = maybe (numContBins-1) (subtract 1) (findIndex (> c) idxs)
      when (RangeSet.sizeRanges (fst set) >= 2) do
        binC <- readArray bins idx
        writeArray bins idx (set : binC)
    szs <- map length <$> getElems bins
    print szs
    return (any (< binSize) szs)

  map (bimap toRatio (map (\(r, xs) -> (r, Set.fromList xs, xs, sort xs)))) <$> getAssocs bins-}