[Haskell-cafe] optimization of recursive functions
wren ng thornton
wren at freegeek.org
Thu Feb 14 04:53:19 CET 2013
On 2/13/13 3:55 AM, Andrew Polonsky wrote:
> Hello,
>
> Is there any difference in efficiency between these two functions, when
> compiled with all optimizations?
>
> map f [] = []
> map f (a:as) = f a : map f as
>
> and
>
> map f x = map' x where
> map' [] = []
> map' (a:as) = f a : map' as
As Alp says, there is some difference since the second definition closes
over f. Whether that closure is an optimization or a pessimization is a
bit fragile. When there are multiple things being closed over, then the
closure is faster than passing things to the recursion. But, at least in
the past, there was a transition point where passing arguments is faster
when there's only one of them; I haven't run benchmarks on newer
versions of GHC to see if that's still true.
The second point is whether these definitions get inlined at their use
sites or not. If they get inlined, then the second has a definite
advantage since we will know f statically and can use a direct call
rather than an indirect call (unless the static f is a locally bound
variable and therefore still unknown). Being able to make indirect calls
direct is one of the major benefits of inlining. However, in new
versions of GHC, it bases decisions about whether to inline or not on
how many arguments the definition has. Thus, you'd get more inlining if
you used the definition:
map f = map'
where
map' [] = []
map' (a:as) = f a : map' as
since this allows inlining when map is only applied to f, rather than
applied to both f and x. Sometimes this extra inlining is good (for the
reasons mentioned above), but sometimes it's bad because it can lead to
code bloat. Remember, the major bottleneck of computing these days is
access to disk and memory. So increasing the size of the compiled file
can lead to slowdown because you end up blowing the instruction cache
and needing to load from memory more often. (The exact details of when
this result occurs are, of course, extremely dependent on the particular
program and what's causing the code bloat.)
In short, the worker/wrapper transform often improves performance, but
for this specific example it's hard to say which is faster. Just
benchmark it! Criterion is an excellent tool for answering questions
like these, though it doesn't quite get at the inlining details. I
wouldn't worry too much about it unless (a) profiling indicated that
this function was a problem, or (b) I had a large client program that I
could test performance with. Of course, if you're concerned about
performance, another thing to look at here is list-fusion which will
allow the compiler to remove intermediate lists.
--
Live well,
~wren
More information about the Haskell-Cafe
mailing list