[GHC] #6040: Adding a type signature changes heap allocation into stack allocation without changing the actual type
GHC
cvs-ghc at haskell.org
Wed Dec 5 21:26:06 CET 2012
#6040: Adding a type signature changes heap allocation into stack allocation
without changing the actual type
---------------------------------+------------------------------------------
Reporter: tibbe | Owner: simonpj
Type: bug | Status: new
Priority: normal | Milestone: 7.8.1
Component: Compiler | Version: 7.4.1
Keywords: | Os: Unknown/Multiple
Architecture: Unknown/Multiple | Failure: None/Unknown
Difficulty: Unknown | Testcase:
Blockedby: | Blocking:
Related: |
---------------------------------+------------------------------------------
Comment(by milan):
I was asked by Johan to demonstrate the issue.
I would like to note that I am not sure it is even a bug -- I understand
what is going on and I am not sure what a better behaviour would be -- but
maybe the transformation Simon suggests is a good idea.
Consider two methods `delete1` and `delete2` which delete all occurences
of a given key in the list:
{{{
module Test ( delete1, delete2 ) where
delete1 :: Eq k => k -> [k] -> [k]
delete1 = go
where go _ [] = []
go k (x:xs) | k == x = go k xs
| otherwise = x : go k xs
delete2 :: Eq k => k -> [k] -> [k]
delete2 = go
where go :: Eq k => k -> [k] -> [k]
go _ [] = []
go k (x:xs) | k == x = go k xs
| otherwise = x : go k xs
}}}
The difference is that in the first case, `go` is a closure which captures
the `Eq` dictionary of the outer function. In the second case, `go` is
being given the `Eq` dictionary as a parameter and therefore floats out of
`delete2`. STG illustrates this nicely:
{{{
==================== STG syntax: ====================
Test.delete1 =
\r [$dEq_sql eta_sqs eta1_sqt]
let {
go_sqq =
sat-only \r [ds_sqm ds1_sqh]
case ds1_sqh of wild_sqO {
[] -> [] [];
: x_sqn xs_sqp ->
case GHC.Classes.== $dEq_sql ds_sqm x_sqn of
wild1_sqP {
GHC.Types.False ->
let { sat_sqN = \u [] go_sqq ds_sqm xs_sqp;
} in : [x_sqn sat_sqN];
GHC.Types.True -> go_sqq ds_sqm xs_sqp;
};
};
} in go_sqq eta_sqs eta1_sqt;
SRT(Test.delete1): []
Test.delete2_go =
\r [$dEq_sqB ds_sqC ds1_sqx]
case ds1_sqx of wild_sqQ {
[] -> [] [];
: x_sqD xs_sqF ->
case GHC.Classes.== $dEq_sqB ds_sqC x_sqD of wild1_sqS {
GHC.Types.False ->
let { sat_sqR = \u [] Test.delete2_go $dEq_sqB ds_sqC
xs_sqF;
} in : [x_sqD sat_sqR];
GHC.Types.True -> Test.delete2_go $dEq_sqB ds_sqC xs_sqF;
};
};
SRT(Test.delete2_go): []
Test.delete2 =
\r [$dEq_sqK eta_sqL eta1_sqM]
Test.delete2_go $dEq_sqK eta_sqL eta1_sqM;
SRT(Test.delete2): []
}}}
The result is that `delete1` has to heap-allocate the dictionary while
`delete2` does not.
{{{
Test_delete1_info()
{ update_frame: <none>
label: Test_delete1_info
rep:HeapRep static { Fun {arity: 3 fun_type: ArgSpec 20} }
}
crz:
Hp = Hp + 8;
if (Hp > HpLim) goto crC;
I32[Hp - 4] = sqq_info;
I32[Hp + 0] = I32[Sp + 0];
R1 = Hp - 2;
Sp = Sp + 4;
jump sqq_info ();
...
}}}
The performance difference is very minor -- `delete1` needs one more two-
word heap allocation. The time difference will not probably be measurable.
I notised this only because I monitor heap allocations in `containers`
code as they cause ghc perf tests which monitor heap allocation to fail.
BTW, we use identical `insert` and `go` functions in containers because
the outer is marked `INLINE` in pre-7.0 GHC.
--
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/6040#comment:4>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the Glasgow-haskell-bugs
mailing list