From 02da00807448b427246b0f96e01a19b4a3461f66 Mon Sep 17 00:00:00 2001 From: malteneuss Date: Mon, 25 Nov 2024 23:12:44 +0100 Subject: [PATCH] Add a performance measuring top-level user guide page (#10539) * 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 * Update doc/how-to-analyze-haskell-code-performance.rst Co-authored-by: Javier Sagredo * 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 * 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 * Update doc/how-to-analyze-haskell-code-performance.rst Co-authored-by: Javier Sagredo * 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 * 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 * 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 * Update doc/how-to-analyze-haskell-code-performance.rst Co-authored-by: AndreasPK * Add top-level performance measuring guide page --------- Co-authored-by: Javier Sagredo Co-authored-by: Artem Pelenitsyn Co-authored-by: AndreasPK Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- doc/cabal-project-description-file.rst | 9 +- ...ow-to-analyze-haskell-code-performance.rst | 161 ++++++++++++++++++ doc/index.rst | 1 + doc/setup-commands.rst | 2 +- 4 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 doc/how-to-analyze-haskell-code-performance.rst diff --git a/doc/cabal-project-description-file.rst b/doc/cabal-project-description-file.rst index f024e540010..6c1260f51a9 100644 --- a/doc/cabal-project-description-file.rst +++ b/doc/cabal-project-description-file.rst @@ -1,3 +1,5 @@ +.. _cabal-project-file: + Project Description — cabal.project File ======================================== @@ -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 ----------------------------- @@ -1302,6 +1305,8 @@ Foreign function interface options ``--extra-framework-dirs=DIR``, which can be specified multiple times. +.. _profiling-options: + Profiling options ^^^^^^^^^^^^^^^^^ @@ -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. @@ -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 diff --git a/doc/how-to-analyze-haskell-code-performance.rst b/doc/how-to-analyze-haskell-code-performance.rst new file mode 100644 index 00000000000..fe117a117bd --- /dev/null +++ b/doc/how-to-analyze-haskell-code-performance.rst @@ -0,0 +1,161 @@ +How to analyze Haskell performance +================================== + +When a Haskell application is slow or uses too much memory, +Cabal and `GHC `__ +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 `__ + 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 `__. + +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 `__ + +.. note:: + + While a :ref:`cabal.project ` 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 + + +When the application finishes, a profiling JSON report (due to option ``-pj``) +is written to a ``.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 `__ + ``.prof``, which can be visualized with `profiteur `__ + or `ghcprofview `__. + - ``-pj`` for a + `JSON report `__ + ``.prof``, which can be visualized with `Speedscope `__. + - ``-l -p`` for a binary + `"eventlog" report `__ + ``.eventlog``, which contains a lot more details and can show you resource usage over time, and can + be converted to JSON with `hs-speedscope `__ + to be visualized with `Speedscope `__. + 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 `__ +visualizer is +`Speedscope `__, +which runs in the browser and can open this JSON file directly. +See the +`Haskell Optimization Handbook `__ +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` 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: + + +The setting ``profiling-detail: late-toplevel`` instructs GHC to use so-called +`late-cost-center profiling `__ +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`). +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 `__. +and the +`GHC profiling compiler options `__ +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`. + - ``package `` to apply to a :ref:`single package`, be it local or remote. + - ``package *`` to apply to :ref:`all local and remote packages (dependencies)`. + +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) + ... + + +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 `. diff --git a/doc/index.rst b/doc/index.rst index 0b5407e8580..c944ed63d09 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -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 diff --git a/doc/setup-commands.rst b/doc/setup-commands.rst index b40b94c95bf..69f9e276cf4 100644 --- a/doc/setup-commands.rst +++ b/doc/setup-commands.rst @@ -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