| ... |
... |
@@ -347,8 +347,21 @@ elemUDFM k (UDFM m _i) = M.member (getKey $ getUnique k) m |
|
347
|
347
|
-- See Note [Cost of deterministic iteration].
|
|
348
|
348
|
foldUDFM :: (elt -> a -> a) -> a -> UniqDFM key elt -> a
|
|
349
|
349
|
{-# INLINE foldUDFM #-}
|
|
350
|
|
--- This INLINE prevents a regression in !10568
|
|
351
|
|
-foldUDFM k z m = foldr k z (eltsUDFM m)
|
|
|
350
|
+-- The INLINE prevents a regression in !10568 and, together with the placement
|
|
|
351
|
+-- sort's build form, lets the non-empty path fold directly over the sorted
|
|
|
352
|
+-- readout with no intermediate list. See Note [Sorting a UDFM].
|
|
|
353
|
+foldUDFM k z (UDFM m ub)
|
|
|
354
|
+ -- n <= 1: any order is trivially tag order, so fold straight over the map
|
|
|
355
|
+ | M.compareSize m 1 /= GT = M.foldr (k . taggedFst) z m
|
|
|
356
|
+ | otherwise = fold_elts_nonempty k z m ub
|
|
|
357
|
+
|
|
|
358
|
+-- Precondition: m is non-empty. Out of line (like elts_nonempty) so foldUDFM's
|
|
|
359
|
+-- consumers don't inline the sort machinery; but placementSort's build fuses
|
|
|
360
|
+-- with the foldr here, so the placement path folds without building a list.
|
|
|
361
|
+fold_elts_nonempty :: (elt -> a -> a) -> a -> M.Word64Map (TaggedVal elt) -> Int -> a
|
|
|
362
|
+fold_elts_nonempty k z m ub
|
|
|
363
|
+ | usePlacement m ub = foldr k z (placementSort ub (\_ tv -> tv) m)
|
|
|
364
|
+ | otherwise = foldr k z (map taggedFst (sort_it m))
|
|
352
|
365
|
|
|
353
|
366
|
-- | Like 'foldUDFM' but the function also receives a key.
|
|
354
|
367
|
--
|
| ... |
... |
@@ -420,13 +433,29 @@ O(ub) -- with no comparisons. The readout is lazy, so consumers that demand |
|
420
|
433
|
only a prefix pay almost nothing beyond the fill (but the fill itself is
|
|
421
|
434
|
unavoidable; see Note [Cost of deterministic iteration]).
|
|
422
|
435
|
|
|
|
436
|
+placementSort emits the readout as a 'build', so foldr/build fusion applies.
|
|
|
437
|
+An eltsUDFM consumer materialises the list (build reduces to cons/nil); a
|
|
|
438
|
+foldUDFM consumer instead has its combiner threaded straight into the readout,
|
|
|
439
|
+folding the sorted elements with no intermediate list. foldUDFM is INLINE and
|
|
|
440
|
+dispatches through the out-of-line fold_elts_nonempty, where the fusion happens
|
|
|
441
|
+once -- so the fold is list-free without inlining the sort into every consumer.
|
|
|
442
|
+
|
|
423
|
443
|
Holes: slots whose tag never occurs keep the initial sentinel, a TaggedVal
|
|
424
|
|
-with tag -1. Real tags are non-negative, so the readout skips on tag < 0.
|
|
425
|
|
-The sentinel's value field is never read, but something must fill it: not a
|
|
426
|
|
-panic thunk -- TaggedVal's strict value field would force it as soon as the
|
|
427
|
|
-readout inspects a hole's tag -- so we borrow the value of an arbitrary map
|
|
428
|
|
-element. (Safe: the callers' guards send maps of size < 2 down a different
|
|
429
|
|
-path, so the map is never empty here.)
|
|
|
444
|
+with tag -1. Real tags are non-negative, so the readout skips on tag < 0 and
|
|
|
445
|
+never reads the sentinel's value field. But that field is strict, so the
|
|
|
446
|
+sentinel still needs a value in WHNF: a panic thunk is out (it would be forced
|
|
|
447
|
+-- and crash -- the moment the readout inspects a hole's tag). We use
|
|
|
448
|
+@unsafeCoerce ()@: () is a static, already-evaluated nullary constructor, so
|
|
|
449
|
+the sentinel is one shared top-level value -- no per-call allocation, and it
|
|
|
450
|
+retains nothing.
|
|
|
451
|
+
|
|
|
452
|
+The unsafeCoerce is safe here because r is always a lifted, boxed (pointer)
|
|
|
453
|
+type -- it comes from the map's elements -- so a pointer to () has the right
|
|
|
454
|
+representation. The GC only ever traces that pointer (() is a valid closure);
|
|
|
455
|
+the value is never evaluated or used as an r, since the readout takes values
|
|
|
456
|
+only from filled slots. Borrowing a real element instead would also type-check
|
|
|
457
|
+but costs a per-call thunk that retains the whole source map until the holes
|
|
|
458
|
+are read.
|
|
430
|
459
|
|
|
431
|
460
|
This sorting method loses when ub is much larger than n = M.size m: ub never
|
|
432
|
461
|
shrinks (overwrites keep bumping it, delete/filter shrink n but not ub). We
|
| ... |
... |
@@ -453,19 +482,15 @@ placementSort :: forall e r. Int |
|
453
|
482
|
-> (M.Key -> TaggedVal e -> TaggedVal r)
|
|
454
|
483
|
-> M.Word64Map (TaggedVal e)
|
|
455
|
484
|
-> [r]
|
|
456
|
|
-{-# INLINE placementSort #-} -- specializes mk into the fill loop
|
|
457
|
|
-placementSort ub mk m = runST (ST (\s0 ->
|
|
458
|
|
- case newSmallArray ub hole s0 of
|
|
459
|
|
- (# s1, marr #) -> case fill marr s1 of
|
|
460
|
|
- (# s2, () #) -> case unsafeFreezeSmallArray marr s2 of
|
|
461
|
|
- (# s3, arr #) -> (# s3, readout arr 0 #)))
|
|
|
485
|
+{-# INLINE placementSort #-} -- specializes mk into the fill loop; makes the
|
|
|
486
|
+ -- readout a good producer that fuses with a
|
|
|
487
|
+ -- consuming fold. See Note [Sorting a UDFM].
|
|
|
488
|
+placementSort ub mk m = build gen
|
|
462
|
489
|
where
|
|
463
|
|
- -- The tag -1 marks unfilled slots; the value field is never read, but
|
|
464
|
|
- -- it is strict, so it needs a real value of type r -- borrow one from
|
|
465
|
|
- -- the map. See Note [Sorting a UDFM].
|
|
|
490
|
+ -- The tag -1 marks unfilled slots; the value field is never read, but it
|
|
|
491
|
+ -- is strict, so it needs a WHNF value of type r. See Note [Sorting a UDFM].
|
|
466
|
492
|
hole :: TaggedVal r
|
|
467
|
|
- hole = case M.findMin m of
|
|
468
|
|
- (k, tv) -> TaggedVal (taggedFst (mk k tv)) (-1)
|
|
|
493
|
+ hole = TaggedVal (unsafeCoerce ()) (-1)
|
|
469
|
494
|
|
|
470
|
495
|
fill :: SmallMutableArray s (TaggedVal r) -> State# s -> (# State# s, () #)
|
|
471
|
496
|
fill marr s = case M.traverseWithKey_ write m of ST st -> st s
|
| ... |
... |
@@ -473,12 +498,22 @@ placementSort ub mk m = runST (ST (\s0 -> |
|
473
|
498
|
write k tv = ST (\s' ->
|
|
474
|
499
|
(# writeSmallArray marr (taggedSnd tv) (mk k tv) s', () #))
|
|
475
|
500
|
|
|
476
|
|
- readout :: SmallArray (TaggedVal r) -> Int -> [r]
|
|
477
|
|
- readout arr j
|
|
478
|
|
- | j >= ub = []
|
|
479
|
|
- | t < 0 = readout arr (j + 1)
|
|
480
|
|
- | otherwise = v : readout arr (j + 1)
|
|
481
|
|
- where TaggedVal v t = indexSmallArray arr j
|
|
|
501
|
+ -- Written as a build so that foldr/build fusion lets a consuming fold
|
|
|
502
|
+ -- (foldUDFM) apply its combiner during readout, with no cons cells.
|
|
|
503
|
+ -- Unfused (eltsUDFM), build reduces to cons/nil and yields the list.
|
|
|
504
|
+ gen :: forall b. (r -> b -> b) -> b -> b
|
|
|
505
|
+ gen cons nil = runST (ST (\s0 ->
|
|
|
506
|
+ case newSmallArray ub hole s0 of
|
|
|
507
|
+ (# s1, marr #) -> case fill marr s1 of
|
|
|
508
|
+ (# s2, () #) -> case unsafeFreezeSmallArray marr s2 of
|
|
|
509
|
+ (# s3, arr #) -> (# s3, readout arr 0 #)))
|
|
|
510
|
+ where
|
|
|
511
|
+ readout :: SmallArray (TaggedVal r) -> Int -> b
|
|
|
512
|
+ readout arr j
|
|
|
513
|
+ | j >= ub = nil
|
|
|
514
|
+ | t < 0 = readout arr (j + 1)
|
|
|
515
|
+ | otherwise = cons v (readout arr (j + 1))
|
|
|
516
|
+ where TaggedVal v t = indexSmallArray arr j
|
|
482
|
517
|
|
|
483
|
518
|
filterUDFM :: (elt -> Bool) -> UniqDFM key elt -> UniqDFM key elt
|
|
484
|
519
|
filterUDFM p (UDFM m i) = UDFM (M.filter (\(TaggedVal v _) -> p v) m) i
|