Simon Jakobi pushed to branch wip/sjakobi/udfm-placement at Glasgow Haskell Compiler / GHC

Commits:

1 changed file:

Changes:

  • compiler/GHC/Types/Unique/DFM.hs
    ... ... @@ -14,6 +14,9 @@ See Note [Unique Determinism] in GHC.Types.Unique for explanation why @Unique@ o
    14 14
     is not deterministic.
    
    15 15
     -}
    
    16 16
     
    
    17
    +{-# LANGUAGE MagicHash #-}
    
    18
    +{-# LANGUAGE UnboxedTuples #-}
    
    19
    +
    
    17 20
     {-# OPTIONS_GHC -Wall #-}
    
    18 21
     
    
    19 22
     module GHC.Types.Unique.DFM (
    
    ... ... @@ -79,6 +82,9 @@ import Data.Functor.Classes (Eq1 (..))
    79 82
     import Data.List (sortBy)
    
    80 83
     import Data.Function (on)
    
    81 84
     import GHC.Types.Unique.FM (UniqFM, nonDetUFMToList, ufmToIntMap, unsafeIntMapToUFM)
    
    85
    +import GHC.Data.SmallArray
    
    86
    +import GHC.Exts (State#)
    
    87
    +import GHC.ST (ST(..), runST)
    
    82 88
     import Unsafe.Coerce
    
    83 89
     import qualified GHC.Data.Word64Set as W
    
    84 90
     
    
    ... ... @@ -92,9 +98,9 @@ import qualified GHC.Data.Word64Set as W
    92 98
     -- order then `udfmToList` returns them in deterministic order.
    
    93 99
     --
    
    94 100
     -- There is an implementation cost: each element is given a serial number
    
    95
    --- as it is added, and `udfmToList` sorts its result by this serial
    
    96
    --- number. So you should only use `UniqDFM` if you need the deterministic
    
    101
    +-- as it is added, and `udfmToList` orders its result by this serial number
    
    102
    +-- (see Note [Placement sort in eltsUDFM]). So you should only use `UniqDFM`
    
    103
    +-- if you need the deterministic property.
    
    97 104
     --
    
    98 105
     -- `foldUDFM` also preserves determinism.
    
    99 106
     --
    
    ... ... @@ -153,11 +159,11 @@ data UniqDFM key ele =
    153 159
                                     -- time. See Note [Overflow on plusUDFM]
    
    154 160
       deriving (Data, Functor)
    
    155 161
     
    
    156
    --- | Deterministic, in O(n log n).
    
    162
    +-- | Deterministic. See Note [Placement sort in eltsUDFM] for the cost.
    
    157 163
     instance Foldable (UniqDFM key) where
    
    158 164
       foldr = foldUDFM
    
    159 165
     
    
    160
    --- | Deterministic, in O(n log n).
    
    166
    +-- | Deterministic. See Note [Placement sort in eltsUDFM] for the cost.
    
    161 167
     instance Traversable (UniqDFM key) where
    
    162 168
       traverse f = fmap listToUDFM_Directly
    
    163 169
                  . traverse (\(u,a) -> (u,) <$> f a)
    
    ... ... @@ -310,7 +316,8 @@ elemUDFM :: Uniquable key => key -> UniqDFM key elt -> Bool
    310 316
     elemUDFM k (UDFM m _i) = M.member (getKey $ getUnique k) m
    
    311 317
     
    
    312 318
     -- | Performs a deterministic fold over the UniqDFM.
    
    313
    --- It's O(n log n) while the corresponding function on `UniqFM` is O(n).
    
    319
    +-- It's O(n) in the common case, with an O(n log n) fallback
    
    320
    +-- (see Note [Placement sort in eltsUDFM]).
    
    314 321
     foldUDFM :: (elt -> a -> a) -> a -> UniqDFM key elt -> a
    
    315 322
     {-# INLINE foldUDFM #-}
    
    316 323
     -- This INLINE prevents a regression in !10568
    
    ... ... @@ -334,11 +341,68 @@ nonDetStrictFoldUDFM k z (UDFM m _i) = foldl' k' z m
    334 341
     eltsUDFM :: UniqDFM key elt -> [elt]
    
    335 342
     {-# INLINE eltsUDFM #-}
    
    336 343
     -- The INLINE makes it a good producer (from the map)
    
    337
    -eltsUDFM (UDFM m _i) = map taggedFst (sort_it m)
    
    344
    +eltsUDFM (UDFM m i)
    
    345
    +  | n <= 1            = map taggedFst (M.elems m)  -- no sorting needed
    
    346
    +  | usePlacement n i  = placementSort i (M.elems m)
    
    347
    +  | otherwise         = map taggedFst (sort_it m)
    
    348
    +  where n = M.size m
    
    338 349
     
    
    339 350
     sort_it :: M.Word64Map (TaggedVal elt) -> [TaggedVal elt]
    
    340 351
     sort_it m = sortBy (compare `on` taggedSnd) (M.elems m)
    
    341 352
     
    
    353
    +-- Note [Placement sort in eltsUDFM]
    
    354
    +-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    355
    +-- Deterministic iteration must order elements by insertion tag. Instead of a
    
    356
    +-- comparison sort -- the list mergesort behind sortBy allocates ~n*log n cons
    
    357
    +-- cells -- we exploit the invariant that in (UDFM m i) all tags are distinct
    
    358
    +-- Ints in [0, i): allocate an array of size i, write each element at index =
    
    359
    +-- its tag, freeze, and read out in index order. That's O(i) work (which
    
    360
    +-- subsumes the O(n) fill, since distinct tags force n <= i), no comparisons,
    
    361
    +-- and the readout is lazy, so consumers that demand only a prefix pay almost
    
    362
    +-- nothing beyond the fill.
    
    363
    +--
    
    364
    +-- Holes: slots whose tag never occurs keep the initial sentinel, a TaggedVal
    
    365
    +-- with tag -1. Real tags are non-negative, so the readout skips on tag < 0;
    
    366
    +-- the sentinel's value field is never touched (it is unsafeCoerced ()).
    
    367
    +--
    
    368
    +-- The array loses when i is much larger than n: i never shrinks (overwrites
    
    369
    +-- keep bumping it, delete/filter shrink n but not i). We compute n = M.size m
    
    370
    +-- (O(n), cheap next to either sort) and fall back to the mergesort when
    
    371
    +-- i > 4 * n. Maps built by plain insertion -- the common case -- have i == n.
    
    372
    +-- The guard also caps the fast path's O(i) at O(n).
    
    373
    +--
    
    374
    +-- Determinism is unaffected: the tags are distinct, so they fully determine
    
    375
    +-- the order; the previous sort never relied on stability.
    
    376
    +
    
    377
    +usePlacement :: Int -> Int -> Bool
    
    378
    +usePlacement n i = i <= 4 * n
    
    379
    +
    
    380
    +-- | Order a list of 'TaggedVal's by tag, by placing each at array index =
    
    381
    +-- its tag. The tags must be distinct and in @[0, i)@.
    
    382
    +-- See Note [Placement sort in eltsUDFM].
    
    383
    +placementSort :: forall r. Int -> [TaggedVal r] -> [r]
    
    384
    +placementSort i tvs = runST (ST (\s0 ->
    
    385
    +  case newSmallArray i hole s0 of
    
    386
    +    (# s1, marr #) -> case fill marr tvs s1 of
    
    387
    +      s2 -> case unsafeFreezeSmallArray marr s2 of
    
    388
    +        (# s3, arr #) -> (# s3, readout arr 0 #)))
    
    389
    +  where
    
    390
    +    hole :: TaggedVal r
    
    391
    +    hole = TaggedVal (unsafeCoerce ()) (-1)
    
    392
    +
    
    393
    +    fill :: SmallMutableArray s (TaggedVal r) -> [TaggedVal r] -> State# s -> State# s
    
    394
    +    fill _    []          s = s
    
    395
    +    fill marr (tv : tvs') s =
    
    396
    +      case writeSmallArray marr (taggedSnd tv) tv s of
    
    397
    +        s' -> fill marr tvs' s'
    
    398
    +
    
    399
    +    readout :: SmallArray (TaggedVal r) -> Int -> [r]
    
    400
    +    readout arr j
    
    401
    +      | j >= i    = []
    
    402
    +      | t < 0     = readout arr (j + 1)
    
    403
    +      | otherwise = v : readout arr (j + 1)
    
    404
    +      where TaggedVal v t = indexSmallArray arr j
    
    405
    +
    
    342 406
     filterUDFM :: (elt -> Bool) -> UniqDFM key elt -> UniqDFM key elt
    
    343 407
     filterUDFM p (UDFM m i) = UDFM (M.filter (\(TaggedVal v _) -> p v) m) i
    
    344 408
     
    
    ... ... @@ -356,11 +420,21 @@ udfmRestrictKeysSet (UDFM val_set i) set =
    356 420
       in UDFM (M.restrictKeys val_set key_set) i
    
    357 421
     
    
    358 422
     -- | Converts `UniqDFM` to a list, with elements in deterministic order.
    
    359
    --- It's O(n log n) while the corresponding function on `UniqFM` is O(n).
    
    423
    +-- It's O(n) in the common case, with an O(n log n) fallback
    
    424
    +-- (see Note [Placement sort in eltsUDFM]).
    
    360 425
     udfmToList :: UniqDFM key elt -> [(Unique, elt)]
    
    361
    -udfmToList (UDFM m _i) =
    
    362
    -  [ (mkUniqueGrimily k, taggedFst v)
    
    363
    -  | (k, v) <- sortBy (compare `on` (taggedSnd . snd)) $ M.toList m ]
    
    426
    +udfmToList (UDFM m i)
    
    427
    +  | n <= 1           = [ (mkUniqueGrimily k, taggedFst v) | (k, v) <- M.toList m ]
    
    428
    +    -- Unlike eltsUDFM, this allocates a fresh TaggedVal + pair per element
    
    429
    +    -- before the sort. If it ever matters, a parallel Word64 array of
    
    430
    +    -- keys filled in the same pass would avoid the eager boxes.
    
    431
    +  | usePlacement n i = placementSort i
    
    432
    +      (M.foldrWithKey (\k tv rest ->
    
    433
    +         TaggedVal (mkUniqueGrimily k, taggedFst tv) (taggedSnd tv) : rest) [] m)
    
    434
    +  | otherwise =
    
    435
    +      [ (mkUniqueGrimily k, taggedFst v)
    
    436
    +      | (k, v) <- sortBy (compare `on` (taggedSnd . snd)) $ M.toList m ]
    
    437
    +  where n = M.size m
    
    364 438
     
    
    365 439
     -- Determines whether two 'UniqDFM's contain the same keys.
    
    366 440
     equalKeysUDFM :: UniqDFM key a -> UniqDFM key b -> Bool