[GHC] #14941: Switching direct type family application to EqPred (~) prevents inlining in code using vector (10x slowdown)
GHC
ghc-devs at haskell.org
Mon Mar 19 19:37:51 UTC 2018
#14941: Switching direct type family application to EqPred (~) prevents inlining in
code using vector (10x slowdown)
-------------------------------------+-------------------------------------
Reporter: nh2 | Owner: (none)
Type: bug | Status: new
Priority: normal | Milestone:
Component: Compiler | Version: 8.2.2
Keywords: | Operating System: Unknown/Multiple
Architecture: | Type of failure: Runtime
Unknown/Multiple | performance bug
Test Case: | Blocked By:
Blocking: | Related Tickets:
Differential Rev(s): | Wiki Page:
-------------------------------------+-------------------------------------
{{{#!hs
selectVectorDestructive2 :: (PrimMonad m, VG.Vector v e, Ord e, Show e,
VG.Mutable v ~ vm)
=> Int -> v e -> vm (PrimState m) e -> Int -> Int -> m ()
-- slow
selectVectorDestructive2 :: (PrimMonad m, VG.Vector v e, Ord e, Show e)
=> Int -> v e -> VG.Mutable v (PrimState m) e -> Int -> Int -> m ()
-- fast
}}}
These two functions are identical except one has `VG.Mutable v ~ vm` as a
constraint, the other one has it in the type signature right of the `=>`.
The second function is 10x faster.
I would expect them to be equally fast.
The code of the functions is identical, I change only the type
declaration.
The slowness happens because with the first function, inlining of
primitives like `unsafeRead` does not happen, and thus also it boxes the
`Int#`s back to `Int`s when calling `unsafeRead`.
In particular, in `-ddump-simpl`, the slow version has
{{{#!hs
$wpartitionLoop2_rgEy
:: forall (m :: * -> *) (vm :: * -> * -> *) e.
(PrimMonad m, MVector vm e, Ord e) =>
vm (PrimState m) e
-> GHC.Prim.Int#
-> e
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> m Int
$wpartitionLoop2_rgEy
= \... ->
let {
endIndex_a7Ay :: Int
...
endIndex_a7Ay = GHC.Types.I# ww2_sfZn } in
...
(VGM.basicUnsafeRead
...
endIndex_a7Ay)
...
}}}
while the fast version has
{{{#!hs
$w$spartitionLoop2_rgUN
:: GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.MutableByteArray# GHC.Prim.RealWorld
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.Int#
-> GHC.Prim.State# GHC.Prim.RealWorld
-> (# GHC.Prim.State# GHC.Prim.RealWorld, Int #)
...
$spartitionLoop2_rgUP
:: VG.Mutable VU.Vector (PrimState IO) Int
-> Int
-> Int
-> Int
-> Int
-> Int
-> GHC.Prim.State# GHC.Prim.RealWorld
-> (# GHC.Prim.State# GHC.Prim.RealWorld, Int #)
}}}
So with `VG.Mutable v (PrimState m) e` in the type signature, GHC managed
to inline + specialise it all the way down to concrete types
(`VUM.MVector`, `IO`, and `Int` as the element type), and consequently
inline `basicUnsafeRead` on unboxed `Int#`.
But with `VG.Mutable v ~ vm`, ghc keeps `vm (PrimState m) e` all the way,
passes around dictionaries, thus eventually cannot inline
`basicUnsafeRead` and re-packs already unboxed values, like `endIndex_a7Ay
= GHC.Types.I# ww2_sfZn`, before passing them into the non-inlined call of
`basicUnsafeRead`, thus making a tight loop allocate that normally
wouldn't allocate.
Why might rewriting the type signature in such a trivial way make this
happen?
----
I have tested this on GHC 8.0.2, GHC 8.2.2, and GHC 8.5 HEAD commit
cc4677c36ee.
Reproducer:
* https://github.com/nh2/haskell-quickselect-median-of-
medians/blob/0efd6293e779fda2d864ec3d75329fb16b8af6d9/Main.hs#L506
* Running instructions are [https://github.com/nh2/haskell-quickselect-
median-of-medians/commit/7a49d673990dfaebdb0ba837c3fbbaae0455dba0 in this
commit message]; for short: `stack exec -- ghc -O --make Main.hs -rtsopts
-ddump-simpl -dsuppress-coercions -fforce-recomp -ddump-to-file -fno-full-
laziness && ./Main +RTS -sstderr`
* For that file, I have pregenerated `-dverbose-core2core` output here:
https://github.com/nh2/haskell-quickselect-median-of-
medians/tree/0efd6293e779fda2d864ec3d75329fb16b8af6d9/slowness-analysis
* I originally just wanted to write a fast median-of-medians
implementation on ZuriHac 2017, but got totally derailed by this
performance problem. The version of it that I link here is a total mess
because of me mauling it to track down this performance issue.
Trying to increase `-funfolding-use-threshold` or `-funfolding-keeness-
factor` did not change the situation.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/14941>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list