[Haskell-cafe] newbie timing question
Cale Gibbard
cgibbard at gmail.com
Fri Jan 26 18:41:12 EST 2007
On 26/01/07, Sean McLaughlin <seanmcl at cmu.edu> wrote:
>
> Hello,
>
> I'm trying to write a simple function to time an application.
>
> -- this doesn't work
>
> time f x =
> do n1 <- CPUTime.getCPUTime
> let res = f x in
> do n2 <- CPUTime.getCPUTime
> return (res,n2 - n1)
>
> On a function that takes 8 seconds to complete, returns
> (True,46000000)
>
> According to the documentation, this time is in picoseconds, making
> this 46 microseconds.
>
The statement "let res = f x" on its own causes essentially no
computation to occur. In your program, res is only getting computed
when the result of that do-block is finally *printed*. Before that,
it's just a pointer to some code which has yet to run. There's no
reason to compute res there, and so it isn't computed.
Note that the amount of time spent computing res might depend on just
how much of it is ever used by the program. Consider the case where
res is an infinite list, for example.
One easy solution would be to print the value of res between the
timers, though that will include the time for show and IO of course.
Another is to use Control.Exception.evaluate to force the evaluation
of res to occur in sequence with the other IO actions. Be aware that
if res is compound data, you may need to sequence the computation of
each of the components of res as well, as Control.Exception.evaluate
will only cause evaluation to occur to the point of determining the
top-level constructor (Weak Head Normal Form).
In order to control evaluation, there is a primitive in Haskell called
seq with the behaviour that evaluating seq x y will cause x to become
evaluated to WHNF before resulting in y. Note that this only occurs
when the seq itself is evaluated. Writing seq x x instead of x is
redundant, as evaluation is being forced anyway, by the time the seq
is seen. Using this, one can write functions which traverse
datastructures and cause them to be fully evaluated as soon as any
part of them is needed.
strictPair (x,y) = x `seq` y `seq` (x,y)
strictList xs = foldr seq xs xs
As a generalisation of this idea, in the module
Control.Parallel.Strategies, there is a class called NFData, with the
function rnf :: (NFData a) => a -> () (note that the type doesn't
really capture the evaluation side-effect -- when the empty tuple
returned is evaluated, the whole structure passed to rnf will be
forced.). Using this, we could write:
strictList xs = rnf xs `seq` xs
Anyway, that's the quick intro to subverting Haskell's lazy evaluation.
Before I go, there's another rather more honest way to do this sort of
thing, provided that you're more interested in the timing for your own
sake rather than having it be a value that your program is interested
in. GHC has a fairly extensive profiler which will produce beautifully
detailed reports on just how much time and memory were spent on each
part of your program, and it will even let you tag subexpressions to
watch and so on. For that, read
http://www.haskell.org/ghc/docs/latest/html/users_guide/profiling.html
Or if you're impatient, just build your program with the compiler
options -prof -auto-all, and then run the resulting executable with
+RTS -p -RTS on the commandline. It'll produce a profiling report
which will show the relative amount of time spent in each part of the
program, or +RTS -P -RTS, which will report the number of ticks and
actual memory used in addition to the percentages.
Hope this helps,
- Cale
More information about the Haskell-Cafe
mailing list