[GHC] #9398: Data.List.cycle is not a good producer
GHC
ghc-devs at haskell.org
Mon Aug 4 06:45:24 UTC 2014
#9398: Data.List.cycle is not a good producer
-------------------------------------+-------------------------------------
Reporter: dfeuer | Owner:
Type: bug | Status: new
Priority: normal | Milestone: 7.8.4
Component: | Version: 7.8.3
libraries/base | Keywords:
Resolution: | Architecture: Unknown/Multiple
Operating System: | Difficulty: Easy (less than 1
Unknown/Multiple | hour)
Type of failure: Runtime | Blocked By:
performance bug | Related Tickets:
Test Case: |
Blocking: |
Differential Revisions: |
-------------------------------------+-------------------------------------
Comment (by dfeuer):
Replying to [comment:12 nomeata]:
I wrote some test functions, using the type
{{{#!hs
type BuildArg a = forall b . (a -> b -> b) -> b -> b
}}}
Generally things look pretty good (lots of Core to look at below, along
with comments you should take with a grain of salt since I'm just a
newbie). I found one case where your definition works significantly worse
than the Prelude's. The bad case I've found:
{{{#!hs
main = print $ foldr (+) 0 $ take 30000000 $ map (* 13) $ cycle
[1,8,4,0,(5::Int)]
}}}
For some reason, your `cycle` implementation allocates a lot, but the
Prelude one runs in constant space.
The basic test translations:
{{{#!hs
cycleBuild :: BuildArg a -> [a]
cycleBuild g = cycle (build g)
}}}
produces
{{{#!hs
cycleBuild
cycleBuild =
\ @ a_a3jG g_a3hO ->
letrec {
cyc_a2iD
cyc_a2iD = g_a3hO (:) cyc_a2iD; } in
cyc_a2iD
}}}
which is obviously good, and obviously better than the result with
`Prelude.cycle`, which is
{{{#!hs
cycleBuild
cycleBuild =
\ @ a_a1Xy g_a1zF ->
case g_a1zF (:) ([]) of wild_a2aX {
[] -> cycle1;
: ipv_a2b3 ipv1_a2b4 ->
letrec {
xs'_a2b1
xs'_a2b1 = ++ wild_a2aX xs'_a2b1; } in
xs'_a2b1
}
}}}
{{{#!hs
cycleAugment :: BuildArg a -> [a] -> [a]
cycleAugment g xs = cycle (augment g xs)
}}}
produces
{{{#!hs
cycleAugment
cycleAugment =
\ @ a_a3j1 g_a3hS xs_a3hT ->
letrec {
cyc_a2iD
cyc_a2iD = g_a3hS (:) (++ xs_a3hT cyc_a2iD); } in
cyc_a2iD
}}}
which I think is likely the best we can do, and clearly better than what
the Prelude gives:
{{{#!hs
cycleAugment
cycleAugment =
\ @ a_a1WT g_a1zJ xs_a1zK ->
case g_a1zJ (:) xs_a1zK of wild_a2aX {
[] -> cycle1;
: ipv_a2b3 ipv1_a2b4 ->
letrec {
xs'_a2b1
xs'_a2b1 = ++ wild_a2aX xs'_a2b1; } in
xs'_a2b1
}
}}}
{{{#!hs
foldCycleBuild :: (a -> b -> b) -> b -> BuildArg a -> b
foldCycleBuild c n g = foldr c n (cycle (build g))
}}}
produces
{{{#!hs
foldCycleBuild
foldCycleBuild =
\ @ a_a3jl @ b_a3jm c_a3hP _ g_a3hR ->
letrec {
cyc_a2iD
cyc_a2iD = g_a3hR c_a3hP cyc_a2iD; } in
cyc_a2iD
}}}
which is obviously good, and ''incomparably'' better than what the Prelude
gives, which I will not paste here because it is very obviously very much
inferior.
The rest of the tests are a little harder for me to interpret, because the
nested letrecs confuse me, but I think they're probably good too.
{{{#!hs
foldCycle c n xs = foldr c n (cycle xs)
}}}
produces
{{{#!hs
$wfoldCycle
$wfoldCycle =
\ @ a_a3lf @ b_a3lg w_s3mE w1_s3mG ->
letrec {
cyc_a2iD
cyc_a2iD =
letrec {
go_a2GG
go_a2GG =
\ ds_a2GH ->
case ds_a2GH of _ {
[] -> cyc_a2iD;
: y_a2GM ys_a2GN -> w_s3mE y_a2GM (go_a2GG ys_a2GN)
}; } in
go_a2GG w1_s3mG; } in
cyc_a2iD
foldCycle
foldCycle =
\ @ a_a3lf @ b_a3lg w_s3mE _ w2_s3mG -> $wfoldCycle w_s3mE w2_s3mG
}}}
{{{#!hs
foldCycleAugment :: (a -> b -> b) -> b -> BuildArg a -> [a] -> b
foldCycleAugment c n g xs = foldr c n (cycle (augment g xs))
}}}
produces
{{{#!hs
$wfoldCycleAugment
$wfoldCycleAugment =
\ @ a_a3hx @ b_a3hy w_s3mD w1_s3mF w2_s3mG ->
letrec {
cyc_a2iD
cyc_a2iD =
w1_s3mF
w_s3mD
(letrec {
go_a2GG
go_a2GG =
\ ds_a2GH ->
case ds_a2GH of _ {
[] -> cyc_a2iD;
: y_a2GM ys_a2GN -> w_s3mD y_a2GM (go_a2GG ys_a2GN)
}; } in
go_a2GG w2_s3mG); } in
cyc_a2iD
foldCycleAugment
foldCycleAugment =
\ @ a_a3hx @ b_a3hy w_s3mD _ w2_s3mF w3_s3mG ->
$wfoldCycleAugment w_s3mD w2_s3mF w3_s3mG
}}}
which I have no idea what to think of, honestly.
{{{#!hs
mapCycle f xs = map f (cycle xs)
}}}
produces
{{{#!hs
mapCycle
mapCycle =
\ @ a_a3lA @ b_a3lB f_a3hJ xs_a3hK ->
letrec {
cyc_a2iD
cyc_a2iD =
letrec {
go_a2GG
go_a2GG =
\ ds_a2GH ->
case ds_a2GH of _ {
[] -> cyc_a2iD;
: y_a2GM ys_a2GN -> : (f_a3hJ y_a2GM) (go_a2GG ys_a2GN)
}; } in
go_a2GG xs_a3hK; } in
cyc_a2iD
}}}
which looks sane enough—it just maps over `xs` and then cycles the result.
I also took a look at
{{{#!hs
cycleMap f xs = cycle (map f xs)
}}}
This produced exactly the same Core, which I think is a positive sign.
The Prelude implementation, on the other hand, gives a rather terrible
result:
{{{#!hs
mapCycle
mapCycle =
\ @ a_a2aw @ b_a2ax f_a1x7 xs_a1x8 ->
case xs_a1x8 of wild_a2aV {
[] -> case cycle1 of wild1_00 { };
: ipv_a2b1 ipv1_a2b2 ->
letrec {
xs'_a2aZ
xs'_a2aZ = ++ wild_a2aV xs'_a2aZ; } in
map f_a1x7 xs'_a2aZ
}
}}}
Yes, it actually cycles the argument and then maps over the
result—horrible in every way.
--
Ticket URL: <http://ghc.haskell.org/trac/ghc/ticket/9398#comment:15>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler
More information about the ghc-tickets
mailing list