Skip to content

Commit 028e187

Browse files
authored
More efficient Eq, Ord for Seq (#1035)
* Add benchmarks * Keep the list based implementation, for now, but define the list comparisons ourself to avoid base's performance issues. On Seq Int and with GHC 9.6.3, benchmark times improve by ~40%.
1 parent 2ad4ea3 commit 028e187

File tree

2 files changed

+55
-5
lines changed

2 files changed

+55
-5
lines changed

containers-tests/benchmarks/Sequence.hs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Control.Applicative
44
import Control.DeepSeq (rnf)
55
import Control.Exception (evaluate)
66
import Control.Monad.Trans.State.Strict
7-
import Test.Tasty.Bench (bench, bgroup, defaultMain, nf)
7+
import Test.Tasty.Bench (bench, bgroup, defaultMain, nf, whnf)
88
import Data.Foldable (foldl', foldr')
99
import qualified Data.Sequence as S
1010
import qualified Data.Foldable
@@ -174,6 +174,16 @@ main = do
174174
, bench "1000" $ nf (S.unstableSortOn id) rs1000
175175
, bench "10000" $ nf (S.unstableSortOn id) rs10000]
176176
]
177+
, bgroup "eq"
178+
[ bench "100/100" $ whnf (\s' -> s' == s') s100
179+
, bench "10000/10000" $ whnf (\s' -> s' == s') s10000
180+
]
181+
, bgroup "compare"
182+
[ bench "100/100" $ whnf (uncurry compare) (s100, s100)
183+
, bench "10000/10000" $ whnf (uncurry compare) (s10000, s10000)
184+
, bench "100/10000" $ whnf (uncurry compare) (s100, s10000)
185+
, bench "10000/100" $ whnf (uncurry compare) (s10000, s100)
186+
]
177187
]
178188

179189
{-

containers/src/Data/Sequence/Internal.hs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -908,10 +908,12 @@ instance Alternative Seq where
908908
(<|>) = (><)
909909

910910
instance Eq a => Eq (Seq a) where
911-
xs == ys = length xs == length ys && toList xs == toList ys
911+
xs == ys = liftEq (==) xs ys
912+
{-# INLINABLE (==) #-}
912913

913914
instance Ord a => Ord (Seq a) where
914-
compare xs ys = compare (toList xs) (toList ys)
915+
compare xs ys = liftCompare compare xs ys
916+
{-# INLINABLE compare #-}
915917

916918
#ifdef TESTING
917919
instance Show a => Show (Seq a) where
@@ -929,11 +931,49 @@ instance Show1 Seq where
929931

930932
-- | @since 0.5.9
931933
instance Eq1 Seq where
932-
liftEq eq xs ys = length xs == length ys && liftEq eq (toList xs) (toList ys)
934+
liftEq eq xs ys =
935+
sameSize xs ys && sameSizeLiftEqLists eq (toList xs) (toList ys)
936+
{-# INLINE liftEq #-}
933937

934938
-- | @since 0.5.9
935939
instance Ord1 Seq where
936-
liftCompare cmp xs ys = liftCompare cmp (toList xs) (toList ys)
940+
liftCompare f xs ys = liftCmpLists f (toList xs) (toList ys)
941+
{-# INLINE liftCompare #-}
942+
943+
-- Note [Eq and Ord]
944+
-- ~~~~~~~~~~~~~~~~~
945+
-- Eq and Ord for Seq are implemented by converting to lists, which turns out
946+
-- to be quite efficient.
947+
-- However, we define our own functions to work with lists because the relevant
948+
-- list functions in base have performance issues (liftEq and liftCompare are
949+
-- recursive and cannot inline, (==) and compare are not INLINABLE and cannot
950+
-- specialize).
951+
952+
-- Same as `length xs == length ys` but uses the structure invariants to skip
953+
-- unnecessary cases.
954+
sameSize :: Seq a -> Seq b -> Bool
955+
sameSize (Seq t1) (Seq t2) = case (t1, t2) of
956+
(EmptyT, EmptyT) -> True
957+
(Single _, Single _) -> True
958+
(Deep v1 _ _ _, Deep v2 _ _ _) -> v1 == v2
959+
_ -> False
960+
961+
-- Assumes the lists are of equal size to skip some cases.
962+
sameSizeLiftEqLists :: (a -> b -> Bool) -> [a] -> [b] -> Bool
963+
sameSizeLiftEqLists eq = go
964+
where
965+
go (x:xs) (y:ys) = eq x y && go xs ys
966+
go _ _ = True
967+
{-# INLINE sameSizeLiftEqLists #-}
968+
969+
liftCmpLists :: (a -> b -> Ordering) -> [a] -> [b] -> Ordering
970+
liftCmpLists cmp = go
971+
where
972+
go [] [] = EQ
973+
go [] (_:_) = LT
974+
go (_:_) [] = GT
975+
go (x:xs) (y:ys) = cmp x y <> go xs ys
976+
{-# INLINE liftCmpLists #-}
937977

938978
instance Read a => Read (Seq a) where
939979
#ifdef __GLASGOW_HASKELL__

0 commit comments

Comments
 (0)