Repository navigation
Make 'length' fusible - #307
Conversation
|
Alright! Stay safe. |
|
I simplified the test: https://gist.github.com/gksato/157a98da55c643f15760720383419b4b |
|
I finally go to review this PR. I digged through code and now I mildly confused. Does original loop @gksato did you try to just use P.S. this just illustrates how badly we need tests for fusion (#229). So far our answer to question does it fuse is "hopes and prayers" which is clearly not enough |
|
@gksato could you possibly submit a test for this, using |
|
@Shimuuar Now I get the situation well. Just rewriting back length = Bundle.length . streamdoes work with GHC 8.8.3. However it doesn't work with GHC 8.0.2, the version that was current at the time a811a86 was introduced, since there is, formally, a loop among -- | /O(1)/ Yield the length of the vector
length :: Vector v a => v a -> Int
{-# INLINE length #-}
length = Bundle.length . stream
-- | /O(1)/ Convert a vector to a 'Bundle'
stream :: Vector v a => v a -> Bundle v a
{-# INLINE_FUSED stream #-}
stream v = Bundle.fromVector v
{-# RULES
...
"New.unstream/stream [Vector]" forall v.
New.unstream (stream v) = clone v
... #-}
-- | Convert a vector to an initialiser which, when run, produces a copy of
-- the vector.
clone :: Vector v a => v a -> New v a
{-# INLINE_FUSED clone #-}
clone v = v `seq` New.create (
do
mv <- M.new (length v)
unsafeCopy mv v
return mv)
-- | /O(n)/ Copy an immutable vector into a mutable one. The two vectors must
-- have the same length. This is not checked.
unsafeCopy
:: (PrimMonad m, Vector v a) => Mutable v (PrimState m) a -> v a -> m ()
{-# INLINE unsafeCopy #-}
unsafeCopy dst src = UNSAFE_CHECK(check) "unsafeCopy" "length mismatch"
(M.length dst == length src)
$ (dst `seq` src `seq` basicUnsafeCopy dst src)Since no |
|
@Bodigrim I'm skimming through |
|
Copy/unsafeCopy/clone require vector to be materialized anyway. So we don't lose anything by switching to basicLength. LGTM @gksato Could you please add info from #307 (comment) to commit message? |
@Shimuuar Oops. I've forgotten to check this PR. Can I reword the commit and force-push? I'll not change the content. |
|
Please do |
This change makes fusible 'Data.Vector.Generic.length', which currently can be inlined but doesn't fuse well. Before this revision, 'length' uses non-fusible 'stream''. This revision replaces 'stream'' with fusible 'stream', and at the same time substitute the mutually-recursive occurrences of 'length' with 'basicLength'. Rationale: The current definition was introduced in the commit a811a86. Prior to that commit, 'length' could not even be inlined with older GHCs, which elected 'length' to be a loop-breaker due to the cyclic references among 'clone', 'unsafeCopy', 'length' and 'stream': * 'clone' refers to 'length' and 'unsafeCopy' in its definition; * 'unsafeCopy' refers to 'length' in its definition; * 'length' referred to 'stream' in its definition; * 'stream' refers to none of the above in its definition, but the rule 'New.unstream/stream' rewrites @'New.unstream' ('stream' v)@ with @'clone' v@. This failure of inlining was reported as haskell#97. To be precise, this cycle of references is immaterial with newer GHCs like GHC 8.8.3. Since 'New.unstream' does not occur in the definitions of 'clone', 'unsafeCopy', and 'length', indefinite inlining will not happen anyway. GHC 8.8.3 seems to detect this immateriality of the referencial loop in question and refrains from setting 'length' as a loop-breaker, even with acdcff3 (the parent commit of a811a86). However with older GHCs, including GHC 8.0.2, regarded the cyclic references fatal and marked 'length' as a loop-breaker. Note that GHC 8.0.2 was current at the time a811a86 was introduced. The commit a811a86 resolved this by replacing 'stream' with 'stream'' in the definition of 'length'. The function 'stream' refers to 'clone' in its fusion rule, but 'stream'' doesn't possess any rule. This cut open the cycle of references and made 'length' inlinable. However, since 'stream'' doesn't possess any rule, defining 'length' = 'Bundle.length' . 'stream'' is all the same as setting 'length' = 'basicLength'. This prevents any stream fusion from happening. This commit resolves this problem by resetting 'length' back to be @'Bundle.length' . 'stream'@, and instead replacing cyclic occurrence of 'length' with 'basicLength' (Just *inlined the simplification result of the definition*). Now 'length' is both inlinable and fusible, even with older GHCs. Fixes: haskell#306 See also: haskell#97 haskell#111 haskell#155
|
@Shimuuar I've done it! |
|
Did you look into inspection-testing by chance? Adding even single test would be great contribution since adding more will become relative straightforward. |
|
In fact, I was just puzzled by the tests in #229, because they include |
|
Here's a small fusion test for this pull request. I picked up @nomeata's work on #229 and modified a bit:
I wasn't really sure it was OK to merge a big mass of tests with this pull request, I left it separate branches. If you know a better-looking way to present these to you, please let me know! |
|
I've merged PR, thanks! It would be better to submit tests as separate PR. It will probably require several iterations to get into working order and I don't think it makes sense to keep this PR unmerged. P.S. Didn't look at tests yet |
This change makes fusible 'Data.Vector.Generic.length', which currently can be inlined but doesn't fuse well. Before this revision, 'length' uses non-fusible 'stream''. This revision replaces 'stream'' with fusible 'stream', and at the same time substitute the mutually-recursive occurrences of 'length' with 'basicLength'. Rationale: The current definition was introduced in the commit a811a86. Prior to that commit, 'length' could not even be inlined with older GHCs, which elected 'length' to be a loop-breaker due to the cyclic references among 'clone', 'unsafeCopy', 'length' and 'stream': * 'clone' refers to 'length' and 'unsafeCopy' in its definition; * 'unsafeCopy' refers to 'length' in its definition; * 'length' referred to 'stream' in its definition; * 'stream' refers to none of the above in its definition, but the rule 'New.unstream/stream' rewrites @'New.unstream' ('stream' v)@ with @'clone' v@. This failure of inlining was reported as #97. To be precise, this cycle of references is immaterial with newer GHCs like GHC 8.8.3. Since 'New.unstream' does not occur in the definitions of 'clone', 'unsafeCopy', and 'length', indefinite inlining will not happen anyway. GHC 8.8.3 seems to detect this immateriality of the referencial loop in question and refrains from setting 'length' as a loop-breaker, even with acdcff3 (the parent commit of a811a86). However with older GHCs, including GHC 8.0.2, regarded the cyclic references fatal and marked 'length' as a loop-breaker. Note that GHC 8.0.2 was current at the time a811a86 was introduced. The commit a811a86 resolved this by replacing 'stream' with 'stream'' in the definition of 'length'. The function 'stream' refers to 'clone' in its fusion rule, but 'stream'' doesn't possess any rule. This cut open the cycle of references and made 'length' inlinable. However, since 'stream'' doesn't possess any rule, defining 'length' = 'Bundle.length' . 'stream'' is all the same as setting 'length' = 'basicLength'. This prevents any stream fusion from happening. This commit resolves this problem by resetting 'length' back to be @'Bundle.length' . 'stream'@, and instead replacing cyclic occurrence of 'length' with 'basicLength' (Just *inlined the simplification result of the definition*). Now 'length' is both inlinable and fusible, even with older GHCs. Fixes: #306 See also: #97 #111 #155
This change resolves #306: it makes fusible
Data.Vector.Generic.length, which currently can be inlined but doesn't fuse well. Before this revision,lengthusesstream', which is non-fusible.This revision replaces
stream'withstream, being fusible, and at the same time substitute the mutually-recursive occurrences oflengthwithbasicLength.Rationale:
The current definition was introduced in the commit a811a86.
Prior to that commit,
lengthcould not even be inlined, because GHC elected it to be a loop-breaker due to the cyclic references amongstream,clone,length, andunsafeCopy. This failure of inlining was reported as #97.The commit a811a86 resolved this by replacing
streamwithstream'in the definition oflength. The functionstreamrefers toclonein its fusion rule, butstream'doesn't possess any rule. This cuts open the cycle of references and makeslengthinlinable.However, since
stream'doesn't possess any rule, definingis all the same as setting
This prevents any stream fusion from happening.
This pull request resolves this problem by resetting
lengthback to beBundle.length . stream, and instead replacing the occurrences oflengthmaking cycles withbasicLength(just inlined the simplification result of the definition). Nowlengthis both inlinable and fusible.The comparison test of the compilation of a simple program is in https://gist.github.com/gksato/417030a1f19f1f376dcb2fabaf66c10a .
Fixes: #306
See also: #97 #111 #155