laziness, memoization and inlining

Hi Everybody, I'm experiencing some undesirable performance behavior, I suspect from inlining things that shouldn't be, defeating my memoization attempts. I've been experimenting with purely functional 3D modeling code, so a mesh is (initially) something like
type Mesh = Map (Int,Int) (Int,Int)
that is, a function from from an edge to the next edge around the face, where an edge is a pair of Ints (the vertices.) This nice and pure and everything, but its slow to read from. So I have another immutable pointer-based representation
data Edge = Edge { edgeOrg :: Int , edgeSym :: Edge , edgeNext :: Edge }
which is something like a half-edge, for those familiar with such things. Its basically a big net made of circular lists that are tied together. I do the knot tying stuff to create this thing,
memoMesh :: Map (Int,Int) (Int,Int) -> Edge Int memoMesh nexts = head $ Map.elems ties where ties = Map.mapWithKey (\ij _ -> make ij) nexts lookup ij = trace "hello" $ fromJust $ Map.lookup ij ties make ij@(i,j) = Edge i (lookup (j,i)) (lookup . fromJust $ Map.lookup ij nexts)
The program first loads the model file and creates the Edge-based mesh using the memoMesh function. The result is then captured in the closure for the rendering callback in GLUT. When I compile with -O0 I see the "hello" traces only during the first drawing. Subsequent redraws are fast and output no traces. When I compile with -O1 or -O2, the traces get output on every redraw, and its very slow. I suspect all of the calls which create the mesh are inlined into the rendering callback, effectively rebuilding the mesh on every draw. I've tried littering NOINLINE pragmas all around, to no avail. The main function is something like
main = do initGlut ... rawMesh <- loadMeshFile ... let mesh = memoMesh rawMesh otherstuff = ... display = draw mesh >> amongOtherThings displayCallback $= display glutMainLoop
Can someone help me understand what's going on here? Is there a nice solution to this, hopefully a single strategic pragma or something? Scott

sedillard:
Hi Everybody,
I'm experiencing some undesirable performance behavior, I suspect from inlining things that shouldn't be, defeating my memoization attempts. I've been experimenting with purely functional 3D modeling code, so a mesh is (initially) something like
type Mesh = Map (Int,Int) (Int,Int)
that is, a function from from an edge to the next edge around the face, where an edge is a pair of Ints (the vertices.)
This nice and pure and everything, but its slow to read from. So I have another immutable pointer-based representation
data Edge = Edge { edgeOrg :: Int , edgeSym :: Edge , edgeNext :: Edge }
which is something like a half-edge, for those familiar with such things. Its basically a big net made of circular lists that are tied together. I do the knot tying stuff to create this thing,
memoMesh :: Map (Int,Int) (Int,Int) -> Edge Int memoMesh nexts = head $ Map.elems ties where ties = Map.mapWithKey (\ij _ -> make ij) nexts lookup ij = trace "hello" $ fromJust $ Map.lookup ij ties make ij@(i,j) = Edge i (lookup (j,i)) (lookup . fromJust $ Map.lookup ij nexts)
The program first loads the model file and creates the Edge-based mesh using the memoMesh function. The result is then captured in the closure for the rendering callback in GLUT. When I compile with -O0 I see the "hello" traces only during the first drawing. Subsequent redraws are fast and output no traces. When I compile with -O1 or -O2, the traces get output on every redraw, and its very slow. I suspect all of the calls which create the mesh are inlined into the rendering callback, effectively rebuilding the mesh on every draw.
Hmm. I wonder if *this* is the no-state-hack at play. Does -fno-state-hack help?
I've tried littering NOINLINE pragmas all around, to no avail.
The main function is something like
main = do initGlut ... rawMesh <- loadMeshFile ... let mesh = memoMesh rawMesh otherstuff = ... display = draw mesh >> amongOtherThings displayCallback $= display glutMainLoop
Can someone help me understand what's going on here? Is there a nice solution to this, hopefully a single strategic pragma or something?
Is it possible to boil this down to a program that doesn't use GL? -- Don

Scott | I'm experiencing some undesirable performance behavior, I suspect from | inlining things that shouldn't be, defeating my memoization attempts. This is bad, very bad. I think Don is right. I believe the following is happening. In your main program you have do let mesh = memoMesh rawMesh display :: IO () display = draw mesh >> stuff setDisplayCallback display glutMainLoop So the effect is that 'display' is performed many times, by glutMainLoop. Now 'display' is seen by GHC thus: display = \s -> draw mesh s >> stuff The "\s" says "given the state of the world, s, I'll draw the mesh on it". The "state hack" makes GHC think that a "\s" will only ever be called once (which is utterly false in this case), so it can inline mesh=memoMesh rawMesh. Result disaster. I bet you'll be fine if you compile your main module with -fno-state-hack. But I should fix this, somehow. It's coming up too often to justify the hack. Can you make a Trac bug report, and include your message and this one? Thanks for reporting it. Simon | -----Original Message----- | From: glasgow-haskell-users-bounces@haskell.org [mailto:glasgow-haskell-users-bounces@haskell.org] On | Behalf Of Scott Dillard | Sent: 14 May 2008 00:13 | To: glasgow-haskell-users@haskell.org | Subject: laziness, memoization and inlining | | Hi Everybody, | | I'm experiencing some undesirable performance behavior, I suspect from | inlining things that shouldn't be, defeating my memoization attempts. | I've been experimenting with purely functional 3D modeling code, so a | mesh is (initially) something like | | > type Mesh = Map (Int,Int) (Int,Int) | | that is, a function from from an edge to the next edge around the | face, where an edge is a pair of Ints (the vertices.) | | This nice and pure and everything, but its slow to read from. So I | have another immutable pointer-based representation | | > data Edge = Edge { edgeOrg :: Int , edgeSym :: Edge , edgeNext :: Edge } | | which is something like a half-edge, for those familiar with such | things. Its basically a big net made of circular lists that are tied | together. I do the knot tying stuff to create this thing, | | > memoMesh :: Map (Int,Int) (Int,Int) -> Edge Int | > memoMesh nexts = head $ Map.elems ties | > where | > ties = Map.mapWithKey (\ij _ -> make ij) nexts | > lookup ij = trace "hello" $ fromJust $ Map.lookup ij ties | > make ij@(i,j) = Edge i (lookup (j,i)) (lookup . fromJust $ Map.lookup ij nexts) | | The program first loads the model file and creates the Edge-based mesh | using the memoMesh function. The result is then captured in the | closure for the rendering callback in GLUT. When I compile with -O0 I | see the "hello" traces only during the first drawing. Subsequent | redraws are fast and output no traces. When I compile with -O1 or -O2, | the traces get output on every redraw, and its very slow. I suspect | all of the calls which create the mesh are inlined into the rendering | callback, effectively rebuilding the mesh on every draw. | | I've tried littering NOINLINE pragmas all around, to no avail. | | The main function is something like | | > main = do | > initGlut ... | > rawMesh <- loadMeshFile ... | > let | > mesh = memoMesh rawMesh | > otherstuff = ... | > display = | > draw mesh >> amongOtherThings | > displayCallback $= display | > glutMainLoop | | Can someone help me understand what's going on here? Is there a nice | solution to this, hopefully a single strategic pragma or something? | | Scott | _______________________________________________ | Glasgow-haskell-users mailing list | Glasgow-haskell-users@haskell.org | http://www.haskell.org/mailman/listinfo/glasgow-haskell-users

Simon, Don,
You're right. -fno-state-hack fixed it. I've opened a trac ticket.
Program and test data are there.
http://hackage.haskell.org/trac/ghc/ticket/2284
Scott
On Wed, May 14, 2008 at 1:48 AM, Simon Peyton-Jones
Scott
| I'm experiencing some undesirable performance behavior, I suspect from | inlining things that shouldn't be, defeating my memoization attempts.
This is bad, very bad. I think Don is right. I believe the following is happening. In your main program you have
do let mesh = memoMesh rawMesh display :: IO () display = draw mesh >> stuff setDisplayCallback display glutMainLoop
So the effect is that 'display' is performed many times, by glutMainLoop.
Now 'display' is seen by GHC thus: display = \s -> draw mesh s >> stuff
The "\s" says "given the state of the world, s, I'll draw the mesh on it". The "state hack" makes GHC think that a "\s" will only ever be called once (which is utterly false in this case), so it can inline mesh=memoMesh rawMesh. Result disaster.
I bet you'll be fine if you compile your main module with -fno-state-hack.
But I should fix this, somehow. It's coming up too often to justify the hack. Can you make a Trac bug report, and include your message and this one?
Thanks for reporting it.
Simon

Scott Dillard wrote:
Simon, Don,
You're right. -fno-state-hack fixed it. I've opened a trac ticket. Program and test data are there.
Ok, but do we really need two tickets for this? Why open a new ticket rather than adding the information to the existing ticket? Cheers, Simon

i'd forgotten there was an existing one when I asked Scott document his problem. I've cross-linked them, but equally good would be to transfer the info | -----Original Message----- | From: Simon Marlow [mailto:simonmarhaskell@gmail.com] On Behalf Of Simon Marlow | Sent: 15 May 2008 10:35 | To: Scott Dillard | Cc: Simon Peyton-Jones; Don Stewart; glasgow-haskell-users@haskell.org | Subject: Re: laziness, memoization and inlining | | Scott Dillard wrote: | > Simon, Don, | > | > You're right. -fno-state-hack fixed it. I've opened a trac ticket. | > Program and test data are there. | > | > http://hackage.haskell.org/trac/ghc/ticket/2284 | | Ok, but do we really need two tickets for this? Why open a new ticket | rather than adding the information to the existing ticket? | | Cheers, | Simon
participants (5)
-
Don Stewart
-
Scott Dillard
-
Scott Dillard
-
Simon Marlow
-
Simon Peyton-Jones