packages feed

linear-base-0.1.0: bench/Data/Mutable/HashMap.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
module Data.Mutable.HashMap (hmbench, getHMInput) where

import Gauge
import qualified System.Random as Random
import qualified System.Random.Shuffle as Random
import Control.DeepSeq (deepseq, force, NFData(..))
import Data.Hashable (Hashable(..), hashWithSalt)
import GHC.Generics (Generic)
import qualified Data.Unrestricted.Linear as Linear
import Data.List (foldl')
import qualified Prelude.Linear as Linear
import Control.Monad.ST (runST, ST)
import Control.Exception (evaluate)

import qualified Data.HashMap.Mutable.Linear as LMap
import qualified Data.HashMap.Strict as Map
import qualified Data.HashTable.ST.Basic as BasicST
import qualified Data.HashTable.ST.Cuckoo as CuckooST


-- # Exported benchmarks
-------------------------------------------------------------------------------

newtype Key = Key Int

deriving instance Eq Key
deriving instance Ord Key
deriving instance Generic Key
deriving instance NFData Key
instance Hashable Key where
    hash (Key x) =
      x  `hashWithSalt` (154669 :: Int)
    -- Note: salt with a prime

data BenchInput where
  BenchInput ::
    { pairs :: ![(Key, Int)] -- Keys paired with values
    , shuffle1 :: ![Key]
    , shuffle2 :: ![Key]
    , shuffle3 :: ![Key]
    } -> BenchInput

hmbench :: BenchInput -> Benchmark
hmbench inp = bgroup "Comparing Linear Hashmaps"
  [ bgroup "linear-base:Data.HashMap.Mutable.Linear" $
      linear_hashmap inp
  , bgroup "unordered-containers:Data.HashMap.Strict" $
      vanilla_hashmap_strict inp
  , bgroup "hashtables:Data.HashTable.ST.Basic" $
      st_basic inp
  , bgroup "hashtables:Data.HashTable.ST.Cuckoo" $
      st_cuckoo inp
  ]

descriptions :: [String]
descriptions =
  -- By "shuffle" we mean we vary the order we access keys
  [ "Insert x, delete x, repeat for whole range"
  , "Insert all, shuffle, modify all"
  , "Insert all, shuffle, lookup all"
  , "Insert all, shuffle, modify all, shuffle, lookup all"
  , "Insert all, shuffle, modify all, shuffle, modify all, shuffle, lookup all"
  ]


-- # Config
-------------------------------------------------------------------------------

num_keys :: Int
num_keys = 100_000

getHMInput :: IO BenchInput
getHMInput = do
  let keys = map Key $ enumFromTo 1 num_keys
  g0 <- Random.getStdGen
  let (gx,gc) = Random.split g0
  let (ga,gb) = Random.split gx
  shuff1 <- evaluate $ force $ Random.shuffle' keys num_keys ga
  shuff2 <- evaluate $ force $ Random.shuffle' shuff1 num_keys gb
  shuff3 <- evaluate $ force $ Random.shuffle' shuff2 num_keys gc
  g1 <- Random.getStdGen
  let (vals :: [Int]) = Random.randomRs (0,num_keys) g1
  kv_pairs <- evaluate $ force (zip keys vals)
  return $ BenchInput kv_pairs shuff1 shuff2 shuff3

modVal :: Maybe Int -> Maybe Int
modVal Nothing = Nothing
modVal (Just !k)
  | even k = Nothing
  | otherwise = Just $ floor (sqrt (fromIntegral k) :: Float) + (2*k) + 1


-- # Linear Hashmaps
-------------------------------------------------------------------------------

linear_hashmap :: BenchInput -> [Benchmark]
linear_hashmap inp@(BenchInput {pairs=kvs}) =
  [bench1, bench2, bench3, bench4, bench5]
  where
    mkBench ::
      Int ->
      ([(Key,Int)] -> LMap.HashMap Key Int %1-> LMap.HashMap Key Int) ->
      Benchmark
    mkBench n f = bench (descriptions!!n) $ nf
      (\xs -> unur $ LMap.empty num_keys Linear.$ kill Linear.. f xs) kvs

    kill :: LMap.HashMap k v %1-> Linear.Ur ()
    kill hmap = Linear.lseq hmap (Linear.Ur ())

    unur :: Linear.Ur a -> a
    unur (Linear.Ur a) = a

    foldlx :: (b %1-> a -> b) -> [a] -> b %1-> b
    foldlx _ [] !b = b
    foldlx f (a:as) !b = foldlx f as (f b a)

    look :: LMap.HashMap Key Int %1-> Key -> LMap.HashMap Key Int
    look hmap k = LMap.lookup k hmap Linear.& \case
      (Linear.Ur Nothing, hmap0) -> hmap0
      (Linear.Ur (Just v), hmap0) -> Linear.seq (force v) hmap0

    insertDelete ::
      LMap.HashMap Key Int %1-> (Key,Int) -> LMap.HashMap Key Int
    insertDelete hmap (c,v) = LMap.delete c (LMap.insert c v hmap)

    bench1 :: Benchmark
    bench1 = mkBench 0 bench1_

    bench1_ :: [(Key,Int)] -> LMap.HashMap Key Int %1-> LMap.HashMap Key Int
    bench1_ xs = foldlx insertDelete xs

    bench2 :: Benchmark
    bench2 = mkBench 1 bench2_

    bench2_ :: [(Key,Int)] -> LMap.HashMap Key Int %1-> LMap.HashMap Key Int
    bench2_ xs =
        foldlx (Linear.flip (LMap.alter modVal)) (shuffle1 inp) Linear..
        LMap.insertAll xs

    bench3 :: Benchmark
    bench3 = mkBench 2 bench3_

    bench3_ :: [(Key,Int)] -> LMap.HashMap Key Int %1-> LMap.HashMap Key Int
    bench3_ xs =
      foldlx look (shuffle1 inp) Linear..
      LMap.insertAll xs

    bench4 :: Benchmark
    bench4 = mkBench 3 bench4_

    bench4_ :: [(Key,Int)] -> LMap.HashMap Key Int %1-> LMap.HashMap Key Int
    bench4_ xs =
      foldlx look (shuffle2 inp) Linear..
      foldlx (Linear.flip (LMap.alter modVal)) (shuffle1 inp) Linear..
      LMap.insertAll xs

    bench5 :: Benchmark
    bench5 = mkBench 4 bench5_

    bench5_ :: [(Key,Int)] -> LMap.HashMap Key Int %1-> LMap.HashMap Key Int
    bench5_ xs =
      foldlx look (shuffle3 inp) Linear..
      foldlx (Linear.flip (LMap.alter modVal)) (shuffle2 inp) Linear..
      foldlx (Linear.flip (LMap.alter modVal)) (shuffle1 inp) Linear..
      LMap.insertAll xs


-- # Vanilla Hashmaps
-------------------------------------------------------------------------------

vanilla_hashmap_strict :: BenchInput -> [Benchmark]
vanilla_hashmap_strict inp@(BenchInput {pairs=kvs}) =
  [bench1, bench2, bench3, bench4, bench5]
  where
    mkBench ::
      Int ->
      ([(Key,Int)] -> Map.HashMap Key Int -> Map.HashMap Key Int) ->
      Benchmark
    mkBench n f =
      bench (descriptions!!n) $ nf (\xs -> f xs Map.empty) kvs

    foldlx :: (b -> a -> b) -> [a] -> b -> b
    foldlx f xs b = foldl' f b xs

    look :: Map.HashMap Key Int -> Key -> Map.HashMap Key Int
    look m k = case m Map.!? k of
      Nothing -> m
      Just v -> deepseq v m

    bench1 :: Benchmark
    bench1 = mkBench 0 $
      \xs hm -> foldl' (\m (k,v) -> Map.delete k (Map.insert k v m)) hm xs

    bench2 :: Benchmark
    bench2 = mkBench 1 $
      \xs ->
        foldlx (flip $ Map.alter modVal) (shuffle1 inp) .
        foldlx (flip $ uncurry Map.insert) xs

    bench3 :: Benchmark
    bench3 = mkBench 2 $
      \xs ->
        foldlx look (shuffle1 inp) .
        foldlx (flip $ uncurry Map.insert) xs

    bench4 :: Benchmark
    bench4 = mkBench 3 $
      \xs ->
        foldlx look (shuffle2 inp) .
        foldlx (flip $ Map.alter modVal) (shuffle1 inp) .
        foldlx (flip $ uncurry Map.insert) xs

    bench5 :: Benchmark
    bench5 = mkBench 4 $
      \xs ->
        foldlx look (shuffle3 inp) .
        foldlx (flip $ Map.alter modVal) (shuffle2 inp) .
        foldlx (flip $ Map.alter modVal) (shuffle1 inp) .
        foldlx (flip $ uncurry Map.insert) xs


-- # ST Basic
-------------------------------------------------------------------------------

st_basic ::  BenchInput -> [Benchmark]
st_basic inp@(BenchInput {pairs=kvs}) =
  [bench1, bench2, bench3, bench4, bench5]
  where
    mkBench ::
      Int ->
      (forall s. [(Key,Int)] -> BasicST.HashTable s Key Int -> ST s ()) ->
      Benchmark
    mkBench n f = bench (descriptions!!n) $ nf
      (\xs -> runST (BasicST.newSized num_keys >>= f xs)) kvs

    look :: BasicST.HashTable s Key Int -> Key -> ST s ()
    look m k = do
      maybeV <- fmap force $ BasicST.lookup m k
      case maybeV of
        Nothing -> return ()
        Just v -> deepseq v (return ())

    bench1 :: Benchmark
    bench1 = mkBench 0 $ \xs hm ->
      mapM_ (\(k,v) -> BasicST.insert hm k v >> BasicST.delete hm k) xs

    bench2 :: Benchmark
    bench2 = mkBench 1 $ \xs hm -> do
      mapM_ (\(k,v) -> BasicST.insert hm k v) xs
      mapM_ (\k -> BasicST.mutate hm k ((,()) . modVal)) (shuffle1 inp)

    bench3 :: Benchmark
    bench3 = mkBench 2 $ \xs hm -> do
      mapM_ (\(k,v) -> BasicST.insert hm k v) xs
      mapM_ (look hm) (shuffle1 inp)

    bench4 :: Benchmark
    bench4 = mkBench 3 $ \xs hm -> do
      mapM_ (\(k,v) -> BasicST.insert hm k v) xs
      mapM_ (\k -> BasicST.mutate hm k ((,()) . modVal)) (shuffle1 inp)
      mapM_ (look hm) (shuffle2 inp)

    bench5 :: Benchmark
    bench5 = mkBench 4 $ \xs hm -> do
      mapM_ (\(k,v) -> BasicST.insert hm k v) xs
      mapM_ (\k -> BasicST.mutate hm k ((,()) . modVal)) (shuffle1 inp)
      mapM_ (\k -> BasicST.mutate hm k ((,()) . modVal)) (shuffle2 inp)
      mapM_ (look hm) (shuffle3 inp)


-- # ST Cuckoo
-------------------------------------------------------------------------------

st_cuckoo ::  BenchInput -> [Benchmark]
st_cuckoo inp@(BenchInput {pairs=kvs}) =
  [bench1, bench2, bench3, bench4, bench5]
  where
    mkBench ::
      Int ->
      (forall s. [(Key,Int)] -> CuckooST.HashTable s Key Int -> ST s ()) ->
      Benchmark
    mkBench n f = bench (descriptions!!n) $ nf
      (\xs -> runST (CuckooST.newSized num_keys >>= f xs)) kvs

    look :: CuckooST.HashTable s Key Int -> Key -> ST s ()
    look m k = do
      maybeV <- fmap force $ CuckooST.lookup m k
      case maybeV of
        Nothing -> return ()
        Just v -> deepseq v (return ())

    bench1 :: Benchmark
    bench1 = mkBench 0 $ \xs hm ->
      mapM_ (\(k,v) -> CuckooST.insert hm k v >> CuckooST.delete hm k) xs

    bench2 :: Benchmark
    bench2 = mkBench 1 $ \xs hm -> do
      mapM_ (\(k,v) -> CuckooST.insert hm k v) xs
      mapM_ (\k -> CuckooST.mutate hm k ((,()) . modVal)) (shuffle1 inp)

    bench3 :: Benchmark
    bench3 = mkBench 2 $ \xs hm -> do
      mapM_ (\(k,v) -> CuckooST.insert hm k v) xs
      mapM_ (look hm) (shuffle1 inp)

    bench4 :: Benchmark
    bench4 = mkBench 3 $ \xs hm -> do
      mapM_ (\(k,v) -> CuckooST.insert hm k v) xs
      mapM_ (\k -> CuckooST.mutate hm k ((,()) . modVal)) (shuffle1 inp)
      mapM_ (look hm) (shuffle2 inp)

    bench5 :: Benchmark
    bench5 = mkBench 4 $ \xs hm -> do
      mapM_ (\(k,v) -> CuckooST.insert hm k v) xs
      mapM_ (\k -> CuckooST.mutate hm k ((,()) . modVal)) (shuffle1 inp)
      mapM_ (\k -> CuckooST.mutate hm k ((,()) . modVal)) (shuffle2 inp)
      mapM_ (look hm) (shuffle3 inp)