[GHC] #10359: Tuple constraint synonym led to asymptotic performance lossage

GHC ghc-devs at haskell.org
Tue Apr 28 00:20:22 UTC 2015


#10359: Tuple constraint synonym led to asymptotic performance lossage
-------------------------------------+-------------------------------------
        Reporter:  axch              |                   Owner:
            Type:  bug               |                  Status:  new
        Priority:  normal            |               Milestone:
       Component:  Compiler          |                 Version:  7.6.3
      Resolution:                    |                Keywords:
Operating System:  Linux             |            Architecture:  x86_64
 Type of failure:  Runtime           |  (amd64)
  performance bug                    |               Test Case:
      Blocked By:                    |                Blocking:
 Related Tickets:                    |  Differential Revisions:
-------------------------------------+-------------------------------------
Description changed by axch:

Old description:

> I encountered a situation where using a tuple constraint synonym
> produced an asymptotic runtime performance degradation, from linear to
> quadratic time.  The enclosed Bug.hs reproduces the potentially
> relevant features of the problem.  The function `test` there is
> quadratic, but should be linear.  The file also contains a definition
> of `test2`, which differs only in eschewing the constraint synonym,
> and is linear, as it should be.
>
> In more detail: the program tries to count to 20,000 in a rather
> contrived way.  It maintains a Box, which holds the current count and
> a function that can accept an increment and a count to produce a new
> count (namely, `(+)`).  The function `do_step` lifts incrementation to
> Boxes, using their contained function to update their stored count.
>
> The first tricky thing is that the incrementing function stored in the
> Box is polymorphic in both arguments separately: it can accept
> increments that are not the same type as the stored count (and
> converts them).  The second tricky thing is that I don't want to
> expose the type variable for the increment type (as opposed to the
> count type) to clients of the Box, so the Box's incrementing function
> is stored polymorphic (with an explicit forall).  Finally, I want to
> impose the constraint `(Fractional num, Real num)` on allowable
> increments (even though this example only needs `Real num`).
>
> This constellation of desiderata conspires to produce quadratic
> performance: doubling the parameter to `test` (but not `test2`)
> multiplies its runtime by 4.  Inspecting the heap profile indicates a
> growing accumulation of closures when running `test` (but not
> `test2`).  Inspecting the generated stg (enclosed) locates a potential
> culprit: apparently when `do_step` (but not `do_step2`) reconstructs
> the Box, it changes the `func` stored there to be a fresh closure that
> destructures the `Numerical` constraint tuple, constructs a fresh one,
> and calls the function that was in that slot previously with it.  I
> hypothesize that this accumulates as a chain that performs a linear
> number of such destructurings and restructurings at each increment,
> leading to the observed quadratic runtime and linear memory growth.
>
> Incidentally, I checked whether record wildcards were the issue (no:
> `test4`) and whether updating just the `obj` field solves it (yes:
> `test3`).  Sadly, the latter solution is not directly usable in my
> actual application because of "Record update for insufficiently
> polymorphic field".

New description:

 I encountered a situation where using a tuple constraint synonym
 produced an asymptotic runtime performance degradation, from linear to
 quadratic time.  The enclosed Bug.hs reproduces the potentially
 relevant features of the problem.  The function `test` there is
 quadratic, but should be linear.  The file also contains a definition
 of `test2`, which differs only in eschewing the constraint synonym,
 and is linear, as it should be.

 In more detail: the program tries to count to 20,000 in a rather
 contrived way.  It maintains a Box, which holds the current count and
 a function that can accept an increment and a count to produce a new
 count (namely, `(+)`).  The function `do_step` lifts incrementation to
 Boxes, using their contained function to update their stored count.

 The first tricky thing is that the incrementing function stored in the
 Box is polymorphic in both arguments separately: it can accept
 increments that are not the same type as the stored count (and
 converts them).  The second tricky thing is that I don't want to
 expose the type variable for the increment type (as opposed to the
 count type) to clients of the Box, so the Box's incrementing function
 is stored polymorphic (with an explicit forall).  Finally, I want to
 impose the constraint `(Fractional num, Real num)` on allowable
 increments (even though this example only needs `Real num`).

 This constellation of desiderata conspires to produce quadratic
 performance: doubling the parameter to `test` (but not `test2`)
 multiplies its runtime by 4.  Inspecting the heap profile indicates a
 growing accumulation of closures when running `test` (but not
 `test2`).  Inspecting the generated stg (enclosed) locates a potential
 culprit: apparently when `do_step` (but not `do_step2`) reconstructs
 the Box, it changes the `func` stored there to be a fresh closure that
 destructures the `Numerical` constraint tuple, constructs a fresh one,
 and calls the function that was in that slot previously with it.  I
 hypothesize that this accumulates as a chain that performs a linear
 number of such destructurings and restructurings at each increment,
 leading to the observed quadratic runtime and linear memory growth.

 Incidentally, I checked whether record wildcards were the issue (no:
 `test4`) and whether updating just the `obj` field solves it (yes:
 `test3`).  Sadly, the latter solution is not directly usable in my
 actual application because of "Record update for insufficiently
 polymorphic field".

 For reference: I compiled with `ghc -O2 Bug.hs -fprof-auto -rtsopts
 -ddump-to-file -ddump-simpl -ddump-st`

--

--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/10359#comment:1>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler


More information about the ghc-tickets mailing list