Personal tools

How to profile a Haskell program

From HaskellWiki

Revision as of 21:16, 1 February 2010 by NicolasFrisby (Talk | contribs)

Jump to: navigation, search


Note: I gave up halfway and moved on to other things. Feel free to take over and steer this article into something useful

Contents

1 The case study

I have a script that converts from an XML format to some pickled data structures via Data.Binary. The XML part is generated by HaXml's DtdToHaskell. On a 54M XML file, the thing swaps like crazy and takes several hours. I would like to improve the situation.

2 Preliminaries

2.1 Preflight checklist

  • Are you using compiler flags for optimised code, e.g. -O2?
  • Are you using the latest version of your libraries?
  • If you just set the an optimisation code, did you remember to make clean (or the equivalent) and rebuild?

2.2 Enable profiling on libraries

For example, my script uses HaXmL, which uses a library called polyparse:

cd polyparse
runhaskell Setup.hs configure --enable-library-profiling
runhaskell Setup.hs build
sudo runhaskell Setup.hs install
cd ..
cd HaXml
runhaskell Setup.hs configure --enable-library-profiling
runhaskell Setup.hs build
sudo runhaskell Setup.hs install

When they are done building, you should notice output like:

ar: creating archive dist/build/libHSpolyparse-1.0.a
ar: creating archive dist/build/libHSpolyparse-1.0_p.a

The _p file is the library with profiling information. Note that the non-profiling one is also created and installed, so you don't have to worry about this slowing down your regular code.

You'll need to do this for every library that you use.

2.3 Enable profiling on your stuff

Note that I assume you are using Cabal. If not, see How to write a Haskell program. It's super easy, and you'll be happy you did it.

If you are looking to profile code that results in an executable, it's pretty straight-forward.

cd yourProgram
runhaskell Setup.hs configure --enable-executable-profiling
runhaskell Setup.hs build

No need to install it. We'll be making changes aplenty.

If you are looking to profile library code that a particular executable invokes, there's one more step. This presentation is specific to GHC's profiling; look to your compiler's manual if you're not using GHC.

cd yourLibrary
runhaskell Setup.hs configure --enable-library-profiling --ghc-option=-auto-all
runhaskell Setup.hs build
sudo runhaskell Setup.hs install

That extra "-auto-all" bit tells the profiler to track the internals of that library more carefully. You might refine the tracking later (see the GHC manual for how), but -auto-all is usually a good place to start.

Let's summarize.

  1. Every library the code you want to profile depends on (transitively) must be compiled with --enable-library-profiling.
  2. Your executable must be compiled with--enable-executable-profiling.
  3. If you want to profile the code of a library, then that library needs to be compiled with GHC's -auto-all option (or comparable for other compilers) in addition to the --enable-library-profiling flag. (This might have a non-GHC-specific Cabal flag after version 1.8, according to this Cabal ticket.)

This article proceeds under the assumption that you are profiling an executable's code, but it's the same basic idea if you're investigating a library's code.

2.4 Get toy data

My script takes hours to convert 50M of XML. Running it on such data every time I tweak something would clearly not be a good idea. You want something which is small enough for your program to come back relatively quickly, but large enough to study.

I use something like sed -f makeToy.sed reallyBigFile.xml > toy.xml where makeToy.sed is a bit of text-hacking to chop off the rest of my data after the arbitrarily chosen item #6621:

/6621/{
c\
</grammar>
q
}

3 Test harness

Make things easy on yourself! I find that it's very helpful to automate my way out of my clumsiness. Ideally, each tweak you make to your software should be accompanied by a simple run and not some long sequence of actions, half of which you might forget. Note: you might also consider using a Makefile instead of a bunch of scripts.

We'll be working with a stable and unstable repository. It's possible that you'll be making a lot of small modifications to your program, so what would be nice is to be able to save some of your modifications along the way. Darcs is very handy for this.

3.1 Create a profiling directory

mkdir profiling
mv toy.xml profiling

3.2 Create a script profiling/setup

#!/bin/sh
chmod u+x profiling/setup
chmod u+x profiling/run
chmod u+x profiling/compare
chmod u+x profiling/save
runhaskell Setup.lhs configure --enable-executable-profiling

3.3 Create a script profiling/run

This script compiles your code, and runs it on some profiling data

#!/bin/sh

PROG=geniconvert
VIEW=open
FLAGS=--yourflags profiling/toydata.xml

runhaskell Setup.lhs build
dist/build/${PROG}/${PROG} ${FLAGS} +RTS  -p -hc -s${PROG}.summary
hp2ps ${PROG}.hp
${VIEW} ${PROG}.ps
cat ${PROG}.summary

3.4 Create a script profiling/compare

3.5 Create a script profiling/save

#!/bin/sh
darcs push --no-set-default ../perfStable
cd ../perfStable
profiling/run

3.6 Create a stable branch

darcs get yourRepository perfStable
cd perfStable
sh profiling/setup
cd ..
cd yourRepository
sh profiling/setup

You should work in the unstable branch (yourRepository). From time to time, you'll want to record your changes and push them into the stable branch. More on this later.

4 Profiling

Generate the data, advice on how to scrutinise it (help especially wanted)

4.1 Generate the data

This should just be:

profiling/run

4.2 Determine what is wrong

4.3 Fix your code

See Performance for ideas, especially Performance/GHC if relevant

4.4 Run it again

profiling/run

4.5 Save the results?

Happy with the direction things are taking?

profiling/save

Go profile again!