Profiling in Haskell can be an overwhelmingly confusing task. There’s plenty of little things you need to set up right to be able to perform it. This post aims to organize this information.
The project must be configured with profiling enabled.
Some specific options must be set when configuring the profiling executable in the “.cabal” file.
All the project’s dependencies must be installed with profiling enabled.
Configuring the project
Well, there’s nothing tricky here. All you need to do is just pass the
--enable-executable-profiling flags to the
configure task. E.g., here is how you configure a project to support profiling as well as running tests and benchmarks:
cabal configure --enable-library-profiling --enable-executable-profiling --enable-tests --enable-benchmarks
Configuring the executable
Here’s an example of a typical setup I use for profiling:
executable my-project-profiling main-is: Profiling.hs ghc-options: -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1" build-depends: base >= 4.5 && < 5
Explanation of the GHC options:
-O2enables aggressive optimization.
-fprof-autoenables automatic cost-centre annotations for profiling. Details here.
-with-rtsoptsbakes in the runtime settings.
Explanation of the runtime settings:
-Nsets the number of available processors for the executable to be equal to the amount of processors on the running system. Otherwise it’s just 1.
-pgenerates a time and allocation profiling report in a “.prof” file. Details here.
-soutputs a report on garbage collection.
-hgenerates a standard report on memory usage. Which can then be used to produce a plot in a postscript file using the “hp2ps” utility. Details here.
-i0.1sets the sampling frequency of memory profiling to every tenth of a second.
Installing the dependencies
This gets trickier. Chances are, most of your dependencies were installed without profiling support and you need to reinstall them. Haskell package management has never been much friendly and it will require you to either drop the whole local packages’ DB and reinstall all the dependencies with profiling enabled, or manually reinstall them all one by one.
Fortunately, since the version 1.17 Cabal comes with a “sandboxes” feature, which maintains an isolated dependencies DB per each project. This is a workaround for numerous Haskell package management issues and is a way to go for us. So before continuing please make sure that you have a proper version of Cabal.
Initialize the project’s sandbox:
cabal sandbox init
Install the dependencies with support for profiling:
cabal install --only-dependencies --enable-library-profiling
Alternatively you can add a “cabal.config” file to the root of your project with the following line:
This will ensure that all the dependencies you install with this project will be with support for profiling. So now you can simply run the following:
cabal install --only-dependencies
With all things set up you can finally run your profiling configuration with the following:
cabal run my-project-profiling && hp2ps -e8in -c my-project-profiling.hp
This command is a two-parter:
cabal run my-project-profilingexecutes our profiling configuration and generates all the reports.
hp2ps -e8in -c my-project-profiling.hpconverts the generated “my-project-profiling.hp” into a postscript graph.
Obviously you have to replace “my-project-profiling” with whatever the name you’ve specified in your cabal configuration.
After running, you’ll get all the reports generated. All that will be left to do is to analyse them. You can find plenty of tutorials on analysing the data throughout the web.