Hi @morphismz.
I feel like profiling Haskell programs is both easy and really cool!
It would be great if this feeling were more widespread. If you come up with a good place to write down the following information more prominently, we should try to put it there.
To profile a Haskell executable from a large Cabal project, you need to, first and foremost, compile your executable with profiling. To do so, pass --enable-profiling
to Cabal, and use late-cost-center profiling for profiling to not interfere with optimisation (--profiling-detail=late
).
cabal build --enable-profiling --profiling-detail=late exe:my-executable-name
Second, you must run the program with the RTS flags to produce a profile. I suggest using -pj
to produce a flame graph profile in JSON that can be loaded into https://speedscope.app. -pj
must be passed after +RTS
, or in between +RTS
and -RTS
, because it is a runtime system option.
cabal list-bin exe:my-executable-name
> /path/to/exe
/path/to/exe +RTS -pj -RTS
> <program is now running>
At this point, you can simply load the my-executable-name.prof
file into https://speedscope.app to get something like:
Profiling your dependencies too
Even though this is plenty good, it’s often useful or needed to profile your dependencies too. The reason why this doesn’t happen by default is because cabal command line flags apply to local packages only. However, all dependencies fetched from hackage count as non-local.
To apply profiling and profiling-detail: late to all packages/dependencies in your project, add to the project’s cabal.project
:
package *
profiling: true
profiling-detail: late
Now, recompile your executable and re-run with +RTS -pj
and reload speedscope to get a more detailed profile with all relevant packages. You should see all packages being built with profiling, for example:
$ cabal build exe:cabal
Resolving dependencies...
Build profile: -w ghc-9.10.1 -O1
In order, the following will be built (use -v for more details):
- base16-bytestring-1.0.2.0 (lib) --enable-profiling (requires build)
- base64-bytestring-1.2.1.0 (lib) --enable-profiling (requires build)
- echo-0.1.4 (lib) --enable-profiling (requires build)
- cryptohash-sha256-0.11.102.1 (lib) --enable-profiling (requires build)
- ed25519-0.0.5.0 (lib) --enable-profiling (requires build)
- hsc2hs-0.68.10 (exe:hsc2hs) --enable-profiling (requires build)
- hashable-1.4.7.0 (lib) --enable-profiling (requires build)
- alex-3.5.1.0 (exe:alex) --enable-profiling (requires build)
- open-browser-0.2.1.0 (lib) --enable-profiling (requires build)
- regex-base-0.94.0.2 (lib) --enable-profiling (requires build)
- safe-exceptions-0.1.7.4 (lib) --enable-profiling (requires build)
- splitmix-0.1.0.5 (lib) --enable-profiling (requires build)
- th-compat-0.1.5 (lib) --enable-profiling (requires build)
- tar-0.6.3.0 (lib:tar-internal) --enable-profiling (requires build)
- resolv-0.2.0.2 (lib:resolv) --enable-profiling (requires build)
- zlib-0.7.1.0 (lib) --enable-profiling (requires build)
- network-3.2.3.0 (lib:network) --enable-profiling (requires build)
- lukko-0.1.2 (lib) --enable-profiling (requires build)
- async-2.2.5 (lib) --enable-profiling (requires build)
- Cabal-syntax-3.13.0.0 (lib) --enable-profiling (cannot read state cache)
- regex-posix-0.96.0.1 (lib) --enable-profiling (requires build)
- random-1.2.1.2 (lib) --enable-profiling (requires build)
- network-uri-2.6.4.2 (lib) --enable-profiling (requires build)
- tar-0.6.3.0 (lib) --enable-profiling (requires build)
- Cabal-3.13.0.0 (lib) --enable-profiling (cannot read state cache)
- edit-distance-0.2.2.1 (lib) --enable-profiling (requires build)
- HTTP-4000.4.1 (lib) --enable-profiling (requires build)
- hackage-security-0.6.2.6 (lib) --enable-profiling (configuration changed)
- cabal-install-solver-3.13.0.0 (lib) --enable-profiling (configuration changed)
- cabal-install-3.13.0.0 (lib) --enable-profiling (configuration changed)
- cabal-install-3.13.0.0 (exe:cabal) --enable-profiling (configuration changed)