Skip to content

Commit 3535fcb

Browse files
committed
Improve the performance of Data.Set balance function.
The balance function is now one monolithic function, which allows to perform all pattern-matches only once. Nearly all functions modifying Data.Map use balance. The improvements are 12% for insert, 14% for delete (GHC 6.12.1).
1 parent 6993f3d commit 3535fcb

File tree

1 file changed

+77
-62
lines changed

1 file changed

+77
-62
lines changed

Data/Set.hs

Lines changed: 77 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -802,85 +802,100 @@ maxView x = Just (deleteFindMax x)
802802
size of one of them. (a rotation).
803803
804804
[delta] is the maximal relative difference between the sizes of
805-
two trees, it corresponds with the [w] in Adams' paper,
806-
or equivalently, [1/delta] corresponds with the $\alpha$
807-
in Nievergelt's paper. Adams shows that [delta] should
808-
be larger than 3.745 in order to garantee that the
809-
rotations can always restore balance.
810-
805+
two trees, it corresponds with the [w] in Adams' paper.
811806
[ratio] is the ratio between an outer and inner sibling of the
812807
heavier subtree in an unbalanced setting. It determines
813808
whether a double or single rotation should be performed
814809
to restore balance. It is correspondes with the inverse
815810
of $\alpha$ in Adam's article.
816811
817-
Note that:
812+
Note that according to the Adam's paper:
818813
- [delta] should be larger than 4.646 with a [ratio] of 2.
819814
- [delta] should be larger than 3.745 with a [ratio] of 1.534.
820-
815+
816+
But the Adam's paper is errorneous:
817+
- it can be proved that for delta=2 and delta>=5 there does
818+
not exist any ratio that would work
819+
- delta=4.5 and ratio=2 does not work
820+
821+
That leaves two reasonable variants, delta=3 and delta=4,
822+
both with ratio=2.
823+
821824
- A lower [delta] leads to a more 'perfectly' balanced tree.
822825
- A higher [delta] performs less rebalancing.
823826
824-
- Balancing is automatic for random data and a balancing
825-
scheme is only necessary to avoid pathological worst cases.
826-
Almost any choice will do in practice
827-
828-
- Allthough it seems that a rather large [delta] may perform better
829-
than smaller one, measurements have shown that the smallest [delta]
830-
of 4 is actually the fastest on a wide range of operations. It
831-
especially improves performance on worst-case scenarios like
832-
a sequence of ordered insertions.
833-
834-
Note: in contrast to Adams' paper, we use a ratio of (at least) 2
835-
to decide whether a single or double rotation is needed. Allthough
836-
he actually proves that this ratio is needed to maintain the
837-
invariants, his implementation uses a (invalid) ratio of 1.
838-
He is aware of the problem though since he has put a comment in his
839-
original source code that he doesn't care about generating a
840-
slightly inbalanced tree since it doesn't seem to matter in practice.
841-
However (since we use quickcheck :-) we will stick to strictly balanced
842-
trees.
827+
In the benchmarks, delta=3 is faster on insert operations,
828+
but delta=4 has better overall performance, so we use delta=4.
829+
830+
Note: in contrast to Adam's paper, we perform the rebalance
831+
even in the case when (size left == delta * size right), instead
832+
when (size left > delta * size) as in the paper. Both are correct,
833+
but the former is slightly faster overall.
834+
843835
--------------------------------------------------------------------}
844836
delta,ratio :: Int
845837
delta = 4
846838
ratio = 2
847839

840+
-- The balance function is equivalent to the following:
841+
--
842+
-- balance :: a -> Set a -> Set a -> Set a
843+
-- balance x l r
844+
-- | sizeL + sizeR <= 1 = Bin sizeX x l r
845+
-- | sizeR >= delta*sizeL = rotateL x l r
846+
-- | sizeL >= delta*sizeR = rotateR x l r
847+
-- | otherwise = Bin sizeX x l r
848+
-- where
849+
-- sizeL = size l
850+
-- sizeR = size r
851+
-- sizeX = sizeL + sizeR + 1
852+
--
853+
-- rotateL :: a -> Set a -> Set a -> Set a
854+
-- rotateL x l r@(Bin _ _ ly ry) | size ly < ratio*size ry = singleL x l r
855+
-- | otherwise = doubleL x l r
856+
-- rotateR :: a -> Set a -> Set a -> Set a
857+
-- rotateR x l@(Bin _ _ ly ry) r | size ry < ratio*size ly = singleR x l r
858+
-- | otherwise = doubleR x l r
859+
--
860+
-- singleL, singleR :: a -> Set a -> Set a -> Set a
861+
-- singleL x1 t1 (Bin _ x2 t2 t3) = bin x2 (bin x1 t1 t2) t3
862+
-- singleR x1 (Bin _ x2 t1 t2) t3 = bin x2 t1 (bin x1 t2 t3)
863+
--
864+
-- doubleL, doubleR :: a -> Set a -> Set a -> Set a
865+
-- doubleL x1 t1 (Bin _ x2 (Bin _ x3 t2 t3) t4) = bin x3 (bin x1 t1 t2) (bin x2 t3 t4)
866+
-- doubleR x1 (Bin _ x2 t1 (Bin _ x3 t2 t3)) t4 = bin x3 (bin x2 t1 t2) (bin x1 t3 t4)
867+
--
868+
-- It is only written in such a way that every node is pattern-matched only once.
869+
848870
balance :: a -> Set a -> Set a -> Set a
849-
balance x l r
850-
| sizeL + sizeR <= 1 = Bin sizeX x l r
851-
| sizeR >= delta*sizeL = rotateL x l r
852-
| sizeL >= delta*sizeR = rotateR x l r
853-
| otherwise = Bin sizeX x l r
854-
where
855-
sizeL = size l
856-
sizeR = size r
857-
sizeX = sizeL + sizeR + 1
858-
859-
-- rotate
860-
rotateL :: a -> Set a -> Set a -> Set a
861-
rotateL x l r@(Bin _ _ ly ry)
862-
| size ly < ratio*size ry = singleL x l r
863-
| otherwise = doubleL x l r
864-
rotateL _ _ Tip = error "rotateL Tip"
865-
866-
rotateR :: a -> Set a -> Set a -> Set a
867-
rotateR x l@(Bin _ _ ly ry) r
868-
| size ry < ratio*size ly = singleR x l r
869-
| otherwise = doubleR x l r
870-
rotateR _ Tip _ = error "rotateL Tip"
871-
872-
-- basic rotations
873-
singleL, singleR :: a -> Set a -> Set a -> Set a
874-
singleL x1 t1 (Bin _ x2 t2 t3) = bin x2 (bin x1 t1 t2) t3
875-
singleL _ _ Tip = error "singleL"
876-
singleR x1 (Bin _ x2 t1 t2) t3 = bin x2 t1 (bin x1 t2 t3)
877-
singleR _ Tip _ = error "singleR"
878-
879-
doubleL, doubleR :: a -> Set a -> Set a -> Set a
880-
doubleL x1 t1 (Bin _ x2 (Bin _ x3 t2 t3) t4) = bin x3 (bin x1 t1 t2) (bin x2 t3 t4)
881-
doubleL _ _ _ = error "doubleL"
882-
doubleR x1 (Bin _ x2 t1 (Bin _ x3 t2 t3)) t4 = bin x3 (bin x2 t1 t2) (bin x1 t3 t4)
883-
doubleR _ _ _ = error "doubleR"
871+
balance x l r = case l of
872+
Tip -> case r of
873+
Tip -> Bin 1 x Tip Tip
874+
r@(Bin rs rx Tip Tip) -> Bin 2 x Tip r
875+
r@(Bin rs rx Tip rr@(Bin _ _ _ _)) -> Bin 3 rx (Bin 1 x Tip Tip) rr
876+
r@(Bin rs rx rl@(Bin _ rlx _ _) Tip) -> Bin 3 rlx (Bin 1 x Tip Tip) (Bin 1 rx Tip Tip)
877+
r@(Bin rs rx rl@(Bin rls rlx rll rlr) rr@(Bin rrs rrx rrl rrr))
878+
| rls < ratio*rrs -> Bin (1+rs) rx (Bin (1+rls) x Tip rl) rr
879+
| otherwise -> Bin (1+rs) rlx (Bin (1+size rll) x Tip rll) (Bin (1+rrs+size rlr) rx rlr rr)
880+
881+
l@(Bin ls lx ll lr) -> case r of
882+
Tip -> case (ll, lr) of
883+
(Tip, Tip) -> Bin 2 x l Tip
884+
(Tip, lr@(Bin _ lrx _ _)) -> Bin 3 lrx (Bin 1 lx Tip Tip) (Bin 1 x Tip Tip)
885+
(ll@(Bin _ _ _ _), Tip) -> Bin 3 lx ll (Bin 1 x Tip Tip)
886+
(ll@(Bin lls llx lll llr), lr@(Bin lrs lrx lrl lrr))
887+
| lrs < ratio*lls -> Bin (1+ls) lx ll (Bin (1+lrs) x lr Tip)
888+
| otherwise -> Bin (1+ls) lrx (Bin (1+lls+size lrl) lx ll lrl) (Bin (1+size lrr) x lrr Tip)
889+
r@(Bin rs rx rl rr)
890+
| rs >= delta*ls -> case (rl, rr) of
891+
(Bin rls rlx rll rlr, Bin rrs rrx rrl rrr)
892+
| rls < ratio*rrs -> Bin (1+ls+rs) rx (Bin (1+ls+rls) x l rl) rr
893+
| otherwise -> Bin (1+ls+rs) rlx (Bin (1+ls+size rll) x l rll) (Bin (1+rrs+size rlr) rx rlr rr)
894+
| ls >= delta*rs -> case (ll, lr) of
895+
(Bin lls llx lll llr, Bin lrs lrx lrl lrr)
896+
| lrs < ratio*lls -> Bin (1+ls+rs) lx ll (Bin (1+rs+lrs) x lr r)
897+
| otherwise -> Bin (1+ls+rs) lrx (Bin (1+lls+size lrl) lx ll lrl) (Bin (1+rs+size lrr) x lrr r)
898+
| otherwise -> Bin (1+ls+rs) x l r
884899

885900

886901
{--------------------------------------------------------------------

0 commit comments

Comments
 (0)