CONTENT="Modular DocBook HTML Stylesheet Version 1.64
Glasgow Haskell comes with a time and space profiling system. Its purpose is to help you improve your understanding of your program's execution behaviour, so you can improve it.
Any comments, suggestions and/or improvements you have are welcome. Recommended “profiling tricks” would be especially cool!
Profiling a program is a three-step process:
GHC's profiling system assigns costs to cost centres. A cost is simply the time or space required to evaluate an expression. Cost centres are program annotations around expressions; all costs incurred by the annotated expression are assigned to the enclosing cost centre. Furthermore, GHC will remember the stack of enclosing cost centres for any given expression at run-time and generate a call-graph of cost attributions.
Let's take a look at an example:
Compile and run this program as follows:
When a GHC-compiled program is run with the -p RTS option, it generates a file called <prog>.prof. In this case, the file will contain something like this:
The first part of the file gives the program name and options, and the total time and total memory allocation measured during the run of the program (note that the total memory allocation figure isn't the same as the amount of live memory needed by the program at any one time; the latter can be determined using heap profiling, which we will describe shortly).
The second part of the file is a break-down by cost centre of the most costly functions in the program. In this case, there was only one significant function in the program, namely nfib, and it was responsible for 100% of both the time and allocation costs of the program.
The third and final section of the file gives a profile break-down by cost-centre stack. This is roughly a call-graph profile of the program. In the example above, it is clear that the costly call to nfib came from main.
The time and allocation incurred by a given part of the program is displayed in two ways: “individual”, which are the costs incurred by the code covered by this cost centre stack alone, and “inherited”, which includes the costs incurred by all the children of this node.
The usefulness of cost-centre stacks is better demonstrated by modifying the example slightly:
Compile and run this program as before, and take a look at the new profiling results:
Now although we had two calls to nfib in the program, it is immediately clear that it was the call from f which took all the time.
The actual meaning of the various columns in the output is:
In addition you can use the -P RTS option to get the following additional information:
What about recursive functions, and mutually recursive groups of functions? Where are the costs attributed? Well, although GHC does keep information about which groups of functions called each other recursively, this information isn't displayed in the basic time and allocation profile, instead the call-graph is flattened into a tree. The XML profiling tool (described in Section 5.5) will be able to display real loops in the call-graph.
Cost centres are just program annotations. When you say -auto-all to the compiler, it automatically inserts a cost centre annotation around every top-level function in your program, but you are entirely free to add the cost centre annotations yourself.
The syntax of a cost centre annotation is
where "name" is an aribrary string, that will become the name of your cost centre as it appears in the profiling output, and <expression> is any Haskell expression. An SCC annotation extends as far to the right as possible when parsing.
The cost of evaluating any expression in your program is attributed to a cost-centre stack using the following rules:
What do we mean by one-off costs? Well, Haskell is a lazy language, and certain expressions are only ever evaluated once. For example, if we write:
then x will only be evaluated once (if at all), and subsequent demands for x will immediately get to see the cached result. The definition x is called a CAF (Constant Applicative Form), because it has no arguments.
For the purposes of profiling, we say that the expression nfib 25 belongs to the one-off costs of evaluating x.
Since one-off costs aren't strictly speaking part of the call-graph of the program, they are attributed to a special top-level cost centre, CAF. There may be one CAF cost centre for each module (the default), or one for each top-level definition with any one-off costs (this behaviour can be selected by giving GHC the -caf-all flag).
If you think you have a weird profile, or the call-graph doesn't look like you expect it to, feel free to send it (and your program) to us at <email@example.com>.