Re: [GHC] #9781: Make list monad operations fuse

From the patch fragment at
https://phabricator.haskell.org/D455?id=1311#inline-3123
What's the justification for expanding out the definition of mapM from
"sequence . map f" into do-notation and duplicated code?
Observe how mapM now duplicates code from sequence.
The absence of benchmarks is bad enough. What's worse is that the given
excuse boils down to "Pity the poor compiler! Let's take over its work
instead. Like, just in case, you know."
This optimization work needs to take place at a higher level, with deep
understanding of existing compiler transformations. Otherwise, it's all a
code-muddying crapshoot in the dark.
-- Kim-Ee
On Tue, Nov 11, 2014 at 2:22 PM, GHC
#9781: Make list monad operations fuse -------------------------------------+------------------------------------- Reporter: dfeuer | Owner: dfeuer Type: task | Status: patch Priority: normal | Milestone: 7.10.1 Component: Core | Version: 7.9 Libraries | Keywords: Resolution: | Architecture: Unknown/Multiple Operating System: | Difficulty: Unknown Unknown/Multiple | Blocked By: Type of failure: Runtime | Related Tickets: performance bug | Test Case: | Blocking: | Differential Revisions: Phab:D455 | -------------------------------------+-------------------------------------
Comment (by Herbert Valerio Riedel
): In [changeset:"4923cea56345060faaf77e4c475eac6aa3c77506/ghc"]: {{{ #!CommitTicketReference repository="ghc" revision="4923cea56345060faaf77e4c475eac6aa3c77506" Define list monad operations using comprehensions
Define list monad operations using list comprehensions. Code using monad operations with lists did not fuse fully. Writing list code with `do` notation or `(>>=)` and `(>>)` operations could allocate more than equivalent code using list comprehensions.
Define `mapM` directly, instead of using `sequence` and `map`. This leads to substantially less allocation in `cryptarithm2`.
Addresses #9781
Reviewed By: ekmett, nomeata
Differential Revision: https://phabricator.haskell.org/D455 }}}
-- Ticket URL: http://ghc.haskell.org/trac/ghc/ticket/9781#comment:4 GHC http://www.haskell.org/ghc/ The Glasgow Haskell Compiler _______________________________________________ ghc-tickets mailing list ghc-tickets@haskell.org http://www.haskell.org/mailman/listinfo/ghc-tickets

On Nov 11, 2014 3:56 AM, "Kim-Ee Yeoh"
From the patch fragment at
https://phabricator.haskell.org/D455?id=1311#inline-3123
What's the justification for expanding out the definition of mapM from
"sequence . map f" into do-notation and duplicated code?
Observe how mapM now duplicates code from sequence.
One good option might be to redefine sequence in terms of mapM.
The absence of benchmarks is bad enough. What's worse is that the given excuse boils down to "Pity the poor compiler! Let's take over its work instead. Like, just in case, you know."
The excuse is that it actually makes a big difference for nofib. Why? I would have to guess it relates to what inlines when. The inliner is a finicky beast. In response to your insults, I will say that although GHC has beautiful ideas in it, a lot of the details of the optimization passes and how they fit together *are* a bit of a crapshoot, chosen by benchmarks rather than theory. Theory sometimes comes up behind and explains why the benchmarks do what they do, but you can't expect every little change to be backed up by some deep theoretical reason.

The inliner is a finicky beast. In response to your insults, I will say that although GHC has beautiful ideas in it, a lot of the details of the optimization passes and how they fit together *are* a bit of a crapshoot, chosen by benchmarks rather than theory.
It’s true that, particularly for fusion, inlining can make a huge difference. And GHC really does need help… it’s extremely hard for it to make the “right” choice all the time.
I strongly agree with Kim-Ee that we should play the game of “optimise by randomly mutating the program and pick the version that (today) happens to run faster”. But I don’t think David is doing that. There is, at least a Note: [List comprehensions and inlining].
What we should do is to
· understand which the crucial inlining decisions are, and why
· express that understanding in a Note, so that subsequent people looking at the code also understand
· remove any un-necessary code cruft, which sometimes accumulates during a period of experimentation, leaving the minimum necessary to achieve the effect
I’m not quite certain that all these steps have been done here, and they can be tiresome to do. But worth it!
Simon
From: Libraries [mailto:libraries-bounces@haskell.org] On Behalf Of David Feuer
Sent: 11 November 2014 09:12
To: Kim-Ee Yeoh
Cc: Haskell Libraries; ghc-devs
Subject: Re: [GHC] #9781: Make list monad operations fuse
On Nov 11, 2014 3:56 AM, "Kim-Ee Yeoh"
From the patch fragment at
https://phabricator.haskell.org/D455?id=1311#inline-3123
What's the justification for expanding out the definition of mapM from "sequence . map f" into do-notation and duplicated code?
Observe how mapM now duplicates code from sequence.
One good option might be to redefine sequence in terms of mapM.
The absence of benchmarks is bad enough. What's worse is that the given excuse boils down to "Pity the poor compiler! Let's take over its work instead. Like, just in case, you know."
The excuse is that it actually makes a big difference for nofib. Why? I would have to guess it relates to what inlines when. The inliner is a finicky beast. In response to your insults, I will say that although GHC has beautiful ideas in it, a lot of the details of the optimization passes and how they fit together *are* a bit of a crapshoot, chosen by benchmarks rather than theory. Theory sometimes comes up behind and explains why the benchmarks do what they do, but you can't expect every little change to be backed up by some deep theoretical reason.

On Nov 11, 2014 6:04 AM, "Simon Peyton Jones"
It’s true that, particularly for fusion, inlining can make a huge difference. And GHC really does need help… it’s extremely hard for it to make the “right” choice all the time.
The inliner does indeed do amazing things, and list fusion does indeed do lovely things for user code. It's just not the most *reliable* optimization in the compiler. I don't think there's anything wrong with admitting that and trying to avoid relying on it too heavily in *library* code. Kim-Ee was right that expanding out mapM by hand bloated the source. I've since defined `sequence=mapM id` to resolve that problem, and doing so does not hurt the benchmarks—it relies only on inlining id (which is quite reliable) and beta-reducing (which is also quite reliable).
I strongly agree with Kim-Ee that we should play the game of “optimise by randomly mutating the program and pick the version that (today) happens to run faster”. But I don’t think David is doing that. There is, at least a Note: [List comprehensions and inlining].
I've been trying to leave a trail of comments and notes as I go. I may need to go further.

Note also that there are fairly clear reasons that fusion is flakier than
many other optimizations. In particular, it requires the compiler to do
things that seem *weird*, and that in most other cases are just *bad
ideas*. At least, it requires:
1. Inlining things that look large and/or expensive. This is done in the
hope that they will fuse with other things, producing a whole that is
smaller and cheaper than the sum of its parts. It is done with the
knowledge that these things will (in most cases) be written back to smaller
cheaper forms if they don't fuse. That is, *our* knowledge of that—the
compiler has absolutely no way to know what shenanigans we're up to.
2. Refraining from floating things that look like they should be floated.
GHC likes to pull constants and such out because doing so improves sharing
and also improves other analyses. But if it pulls our producer away from
our consumer, they will not fuse. I think Simon's simplifier changes a few
months ago helped with this issue, but I don't know that it is (or can ever
be) resolved completely.
On Nov 11, 2014 11:54 AM, "David Feuer"
On Nov 11, 2014 6:04 AM, "Simon Peyton Jones"
wrote: It’s true that, particularly for fusion, inlining can make a huge difference. And GHC really does need help… it’s extremely hard for it to make the “right” choice all the time.
The inliner does indeed do amazing things, and list fusion does indeed do lovely things for user code. It's just not the most *reliable* optimization in the compiler. I don't think there's anything wrong with admitting that and trying to avoid relying on it too heavily in *library* code. Kim-Ee was right that expanding out mapM by hand bloated the source. I've since defined `sequence=mapM id` to resolve that problem, and doing so does not hurt the benchmarks—it relies only on inlining id (which is quite reliable) and beta-reducing (which is also quite reliable).
I strongly agree with Kim-Ee that we should play the game of “optimise by randomly mutating the program and pick the version that (today) happens to run faster”. But I don’t think David is doing that. There is, at least a Note: [List comprehensions and inlining].
I've been trying to leave a trail of comments and notes as I go. I may need to go further.
participants (3)
-
David Feuer
-
Kim-Ee Yeoh
-
Simon Peyton Jones