Skip to content

Commit

Permalink
Add a performance measuring top-level user guide page (#10539)
Browse files Browse the repository at this point in the history
* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Javier Sagredo <jasataco@gmail.com>

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Javier Sagredo <jasataco@gmail.com>

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Artem Pelenitsyn <a.pelenitsyn@gmail.com>

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Javier Sagredo <jasataco@gmail.com>

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Javier Sagredo <jasataco@gmail.com>

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Artem Pelenitsyn <a.pelenitsyn@gmail.com>

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: Javier Sagredo <jasataco@gmail.com>

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Add top-level performance measuring guide page

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: AndreasPK <klebinger.andreas@gmx.at>

* Update doc/how-to-analyze-haskell-code-performance.rst

Co-authored-by: AndreasPK <klebinger.andreas@gmx.at>

* Add top-level performance measuring guide page

---------

Co-authored-by: Javier Sagredo <jasataco@gmail.com>
Co-authored-by: Artem Pelenitsyn <a.pelenitsyn@gmail.com>
Co-authored-by: AndreasPK <klebinger.andreas@gmx.at>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
5 people authored Nov 25, 2024
1 parent 1103d01 commit 02da008
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 2 deletions.
9 changes: 8 additions & 1 deletion doc/cabal-project-description-file.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _cabal-project-file:

Project Description — cabal.project File
========================================

Expand Down Expand Up @@ -767,6 +769,7 @@ The following settings control the behavior of the dependency solver:
explicitly constrained. When set to `none`, the solver will
consider all packages.

.. _package-configuration-options:

Package configuration options
-----------------------------
Expand Down Expand Up @@ -1302,6 +1305,8 @@ Foreign function interface options
``--extra-framework-dirs=DIR``, which can be specified multiple
times.

.. _profiling-options:

Profiling options
^^^^^^^^^^^^^^^^^

Expand All @@ -1328,6 +1333,8 @@ Profiling options
The command line variant of this flag is ``--enable-profiling`` and
``--disable-profiling``.

.. _profiling-detail:

.. cfg-field:: profiling-detail: level
--profiling-detail=level
:synopsis: Profiling detail level.
Expand Down Expand Up @@ -1367,7 +1374,7 @@ Profiling options
late-toplevel
Like top-level but costs will be assigned to top level definitions after
optimization. This lowers profiling overhead massively while giving similar
levels of detail as toplevle-functions. However it means functions introduced
levels of detail as toplevel-functions. However it means functions introduced
by GHC during optimization will show up in profiles as well.
Corresponds to ``-fprof-late`` if supported and ``-fprof-auto-top`` otherwise.
late
Expand Down
161 changes: 161 additions & 0 deletions doc/how-to-analyze-haskell-code-performance.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
How to analyze Haskell performance
==================================

When a Haskell application is slow or uses too much memory,
Cabal and `GHC <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html>`__
can help you understand why. The main steps are:

1. Configure the project in a way that makes GHC insert performance-measuring code into your application.
2. Run the application with the right
`runtime system (RTS) flags <https://downloads.haskell.org/ghc/latest/docs/users_guide/runtime_control.html>`__
to produce a performance report.
3. Visualize and analyze that report.

The process of inserting performance measuring code and collecting performance information
is called "profiling".
This guide describes how to instruct Cabal to pass desired profiling flags to the GHC compiler;
Cabal acts as a convenient build configuration interface while the work is done by GHC.
To get a deeper understanding of the overall profiling process itself in GHC,
it is highly recommended to read in depth the
`Profiling section in GHC's User Guide <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html>`__.

Profiling CPU performance
-------------------------

First, configure Cabal to build your application, e.g. ``my-app``, with profiling enabled,
with the following command:

.. code-block:: console
$ cabal configure --enable-profiling
This command creates a ``cabal.project.local`` file with the following content:

.. code-block:: cabal
profiling: True
This file stores temporary configuration settings that are passed implicitly to further Cabal commands
like ``cabal build`` and ``cabal run``.
The setting ``profiling: True`` tells GHC to build your application (and its dependencies) with profiling enabled,
and to insert performance measuring code into your application.
Where exactly such code is inserted can be controlled with settings like ``profiling-detail``
that are presented later.
Further in-depth information on profiling with GHC and its compiler options can be found in the
`GHC profiling guide <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html>`__

.. note::

While a :ref:`cabal.project <cabal-project-file>` file is intended for long-time settings
that are useful to store in Git, ``cabal.project.local`` is for short-lived, local experiments
(like profiling) that, in general, shouldn't be committed to Git.

Second, run your application with the right runtime system flags and let it create a profiling report:

.. code-block:: console
$ cabal run my-app +RTS -pj -RTS
<app builds, runs and finishes>
When the application finishes, a profiling JSON report (due to option ``-pj``)
is written to a ``<app-name>.prof`` file, i.e. ``my-app.prof``, in the current directory.

.. note::

Different report formats can be generated by using different RTS flags. Some useful ones are:

- ``-p`` for a GHC's own
`standard report <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#cost-centres-and-cost-centre-stacks>`__
``<app-name>.prof``, which can be visualized with `profiteur <https://github.com/jaspervdj/profiteur>`__
or `ghcprofview <https://github.com/portnov/ghcprofview-hs>`__.
- ``-pj`` for a
`JSON report <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#json-profile-format>`__
``<app-name>.prof``, which can be visualized with `Speedscope <https://speedscope.app>`__.
- ``-l -p`` for a binary
`"eventlog" report <https://downloads.haskell.org/ghc/latest/docs/users_guide/runtime_control.html#rts-eventlog>`__
``<app-name>.eventlog``, which contains a lot more details and can show you resource usage over time, and can
be converted to JSON with `hs-speedscope <https://github.com/mpickering/hs-speedscope>`__
to be visualized with `Speedscope <https://speedscope.app>`__.
This will also generate a ``.prof`` file (due to ``-p``), which you can ignore.
We just need the ``-p`` flag for the ``.eventlog`` file to include profiling information.

Finally, visualize this JSON report ``my-app.prof`` and analyze it for performance bottlenecks.
One popular open-source
`flame graph <https://www.brendangregg.com/flamegraphs.html>`__
visualizer is
`Speedscope <https://speedscope.app>`__,
which runs in the browser and can open this JSON file directly.
See the
`Haskell Optimization Handbook <https://haskell.foundation/hs-opt-handbook.github.io>`__
on how to optimize your code based on the profiling results afterwards.

So far, we’ve only used a single Cabal option to enable profiling in general for your application.
Where and when GHC should insert performance measuring code can be controlled with the ``profiling-detail`` setting
and ``ghc-options``.
Leaving ``profiling-detail`` unspecified as before results in sensible defaults that differ between libraries and executable.
See the docs for :ref:`profiling-detail<profiling-detail>` to see which options are available.
You can provide ``profiling-detail`` settings and more compiler flags to GHC
(such as ``-fno-prof-count-entries``) via the ``cabal.project.local`` file:

.. code-block:: cabal
profiling: True
profiling-detail: late-toplevel
program-options
ghc-options:
<further options>
The setting ``profiling-detail: late-toplevel`` instructs GHC to use so-called
`late-cost-center profiling <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#ghc-flag--fprof-late>`__
and insert measuring code only after important optimisations have been applied to your application code.
This reduces the performance slow-down of profiling itself and gives you more realistic measurements.

The ``program-options`` section allows you to add more settings like GHC options to the local
packages of your project (See :ref:`Program options<program_options>`).
The ``ghc-options`` setting allows you to further control which functions and other bindings
the GHC compiler should profile, as well as other aspects of profiling.
You can find more information and further options in the
`GHC "cost-center" guide <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#automatically-placing-cost-centres>`__.
and the
`GHC profiling compiler options <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#compiler-options-for-profiling>`__
section.

Profiling your dependencies too
-------------------------------

The profiling setup so far with the ``cabal.project.local`` file only applied to your local packages,
which is usually what you want.
However, bottlenecks may also exist in your dependencies, so you may want to profile those too.

First, to enable ``late``-cost-center profiling for all packages (including dependencies) concerning your project,
not just the local ones, add the following to your project’s ``cabal.project.local`` file:

.. code-block:: cabal
package *
profiling-detail: late-toplevel
.. note::

There are several keywords to specify to which parts of your project some settings should be applied:

- ``program-options`` to apply to :ref:`all local packages<program_options>`.
- ``package <package-name>`` to apply to a :ref:`single package<package-configuration-options>`, be it local or remote.
- ``package *`` to apply to :ref:`all local and remote packages (dependencies)<package-configuration-options>`.

Second, rerun your application with ``cabal run``, which also automatically rebuilds your application:

.. code-block:: console
$ cabal run my-app -- +RTS -pj -RTS
Resolving dependencies...
Build profile: -w ghc-9.10.1 -O1
In order, the following will be built (use -v for more details):
- base64-bytestring-1.2.1.0 (lib) --enable-profiling (requires build)
- cryptohash-sha256-0.11.102.1 (lib) --enable-profiling (requires build)
...
<app runs and finishes>
You can now find profiling data of dependencies in the report ``my-app.prof``
to analyze. More information on how to configure Cabal options can be found in the
:ref:`Cabal options sections <package-configuration-options>`.
1 change: 1 addition & 0 deletions doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Welcome to the Cabal User Guide

how-to-package-haskell-code
how-to-source-packages
how-to-analyze-haskell-code-performance
how-to-build-like-nix
how-to-run-in-windows
how-to-use-backpack
Expand Down
2 changes: 1 addition & 1 deletion doc/setup-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ Miscellaneous options
late-toplevel
Like top-level but costs will be assigned to top level definitions after
optimization. This lowers profiling overhead massively while giving similar
levels of detail as toplevle-functions. However it means functions introduced
levels of detail as toplevel-functions. However it means functions introduced
by GHC during optimization will show up in profiles as well.
Corresponds to ``-fprof-late`` if supported and ``-fprof-auto-top`` otherwise.
late
Expand Down

0 comments on commit 02da008

Please sign in to comment.