|
| 1 | +How to analyze Haskell performance |
| 2 | +================================== |
| 3 | + |
| 4 | +When a Haskell application is slow or uses too much memory, |
| 5 | +Cabal and `GHC <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html>`__ |
| 6 | +can help you understand why. The main steps are: |
| 7 | + |
| 8 | +1. Configure the project in a way that makes GHC insert performance-measuring code into your application. |
| 9 | +2. Run the application with the right |
| 10 | + `runtime system (RTS) flags <https://downloads.haskell.org/ghc/latest/docs/users_guide/runtime_control.html>`__ |
| 11 | + to produce a performance report. |
| 12 | +3. Visualize and analyze that report. |
| 13 | + |
| 14 | +The process of inserting performance measuring code and collecting performance information |
| 15 | +is called "profiling". |
| 16 | +This guide describes how to instruct Cabal to pass desired profiling flags to the GHC compiler; |
| 17 | +Cabal acts as a convenient build configuration interface while the work is done by GHC. |
| 18 | +To get a deeper understanding of the overall profiling process itself in GHC, |
| 19 | +it is highly recommended to read in depth the |
| 20 | +`Profiling section in GHC's User Guide <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html>`__. |
| 21 | + |
| 22 | +Profiling CPU performance |
| 23 | +------------------------- |
| 24 | + |
| 25 | +First, configure Cabal to build your application, e.g. ``my-app``, with profiling enabled, |
| 26 | +with the following command: |
| 27 | + |
| 28 | +.. code-block:: console |
| 29 | +
|
| 30 | + $ cabal configure --enable-profiling |
| 31 | +
|
| 32 | +This command creates a ``cabal.project.local`` file with the following content: |
| 33 | + |
| 34 | +.. code-block:: cabal |
| 35 | +
|
| 36 | + profiling: True |
| 37 | +
|
| 38 | +This file stores temporary configuration settings that are passed implicitly to further Cabal commands |
| 39 | +like ``cabal build`` and ``cabal run``. |
| 40 | +The setting ``profiling: True`` tells GHC to build your application (and its dependencies) with profiling enabled, |
| 41 | +and to insert performance measuring code into your application. |
| 42 | +Where exactly such code is inserted can be controlled with settings like ``profiling-detail`` |
| 43 | +that are presented later. |
| 44 | +Further in-depth information on profiling with GHC and its compiler options can be found in the |
| 45 | +`GHC profiling guide <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html>`__ |
| 46 | + |
| 47 | +.. note:: |
| 48 | + |
| 49 | + While a :ref:`cabal.project <cabal-project-file>` file is intended for long-time settings |
| 50 | + that are useful to store in Git, ``cabal.project.local`` is for short-lived, local experiments |
| 51 | + (like profiling) that, in general, shouldn't be committed to Git. |
| 52 | + |
| 53 | +Second, run your application with the right runtime system flags and let it create a profiling report: |
| 54 | + |
| 55 | +.. code-block:: console |
| 56 | +
|
| 57 | + $ cabal run my-app +RTS -pj -RTS |
| 58 | + <app builds, runs and finishes> |
| 59 | +
|
| 60 | +When the application finishes, a profiling JSON report (due to option ``-pj``) |
| 61 | +is written to a ``<app-name>.prof`` file, i.e. ``my-app.prof``, in the current directory. |
| 62 | + |
| 63 | +.. note:: |
| 64 | + |
| 65 | + Different report formats can be generated by using different RTS flags. Some useful ones are: |
| 66 | + |
| 67 | + - ``-p`` for a GHC's own |
| 68 | + `standard report <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#cost-centres-and-cost-centre-stacks>`__ |
| 69 | + ``<app-name>.prof``, which can be visualized with `profiteur <https://github.com/jaspervdj/profiteur>`__ |
| 70 | + or `ghcprofview <https://github.com/portnov/ghcprofview-hs>`__. |
| 71 | + - ``-pj`` for a |
| 72 | + `JSON report <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#json-profile-format>`__ |
| 73 | + ``<app-name>.prof``, which can be visualized with `Speedscope <https://speedscope.app>`__. |
| 74 | + - ``-l -p`` for a binary |
| 75 | + `"eventlog" report <https://downloads.haskell.org/ghc/latest/docs/users_guide/runtime_control.html#rts-eventlog>`__ |
| 76 | + ``<app-name>.eventlog``, which contains a lot more details and can show you resource usage over time, and can |
| 77 | + be converted to JSON with `hs-speedscope <https://github.com/mpickering/hs-speedscope>`__ |
| 78 | + to be visualized with `Speedscope <https://speedscope.app>`__. |
| 79 | + This will also generate a ``.prof`` file (due to ``-p``), which you can ignore. |
| 80 | + We just need the ``-p`` flag for the ``.eventlog`` file to include profiling information. |
| 81 | + |
| 82 | +Finally, visualize this JSON report ``my-app.prof`` and analyze it for performance bottlenecks. |
| 83 | +One popular open-source |
| 84 | +`flame graph <https://www.brendangregg.com/flamegraphs.html>`__ |
| 85 | +visualizer is |
| 86 | +`Speedscope <https://speedscope.app>`__, |
| 87 | +which runs in the browser and can open this JSON file directly. |
| 88 | +See the |
| 89 | +`Haskell Optimization Handbook <https://haskell.foundation/hs-opt-handbook.github.io>`__ |
| 90 | +on how to optimize your code based on the profiling results afterwards. |
| 91 | + |
| 92 | +So far, we’ve only used a single Cabal option to enable profiling in general for your application. |
| 93 | +Where and when GHC should insert performance measuring code can be controlled with the ``profiling-detail`` setting |
| 94 | +and ``ghc-options``. |
| 95 | +Leaving ``profiling-detail`` unspecified as before results in sensible defaults that differ between libraries and executable. |
| 96 | +See the docs for :ref:`profiling-detail<profiling-detail>` to see which options are available. |
| 97 | +You can provide ``profiling-detail`` settings and more compiler flags to GHC |
| 98 | +(such as ``-fno-prof-count-entries``) via the ``cabal.project.local`` file: |
| 99 | + |
| 100 | +.. code-block:: cabal |
| 101 | +
|
| 102 | + profiling: True |
| 103 | + profiling-detail: late-toplevel |
| 104 | + program-options |
| 105 | + ghc-options: |
| 106 | + <further options> |
| 107 | +
|
| 108 | +The setting ``profiling-detail: late-toplevel`` instructs GHC to use so-called |
| 109 | +`late-cost-center profiling <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#ghc-flag--fprof-late>`__ |
| 110 | +and insert measuring code only after important optimisations have been applied to your application code. |
| 111 | +This reduces the performance slow-down of profiling itself and gives you more realistic measurements. |
| 112 | + |
| 113 | +The ``program-options`` section allows you to add more settings like GHC options to the local |
| 114 | +packages of your project (See :ref:`Program options<program_options>`). |
| 115 | +The ``ghc-options`` setting allows you to further control which functions and other bindings |
| 116 | +the GHC compiler should profile, as well as other aspects of profiling. |
| 117 | +You can find more information and further options in the |
| 118 | +`GHC "cost-center" guide <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#automatically-placing-cost-centres>`__. |
| 119 | +and the |
| 120 | +`GHC profiling compiler options <https://downloads.haskell.org/ghc/latest/docs/users_guide/profiling.html#compiler-options-for-profiling>`__ |
| 121 | +section. |
| 122 | + |
| 123 | +Profiling your dependencies too |
| 124 | +------------------------------- |
| 125 | + |
| 126 | +The profiling setup so far with the ``cabal.project.local`` file only applied to your local packages, |
| 127 | +which is usually what you want. |
| 128 | +However, bottlenecks may also exist in your dependencies, so you may want to profile those too. |
| 129 | + |
| 130 | +First, to enable ``late``-cost-center profiling for all packages (including dependencies) concerning your project, |
| 131 | +not just the local ones, add the following to your project’s ``cabal.project.local`` file: |
| 132 | + |
| 133 | +.. code-block:: cabal |
| 134 | +
|
| 135 | + package * |
| 136 | + profiling-detail: late-toplevel |
| 137 | +
|
| 138 | +.. note:: |
| 139 | + |
| 140 | + There are several keywords to specify to which parts of your project some settings should be applied: |
| 141 | + |
| 142 | + - ``program-options`` to apply to :ref:`all local packages<program_options>`. |
| 143 | + - ``package <package-name>`` to apply to a :ref:`single package<package-configuration-options>`, be it local or remote. |
| 144 | + - ``package *`` to apply to :ref:`all local and remote packages (dependencies)<package-configuration-options>`. |
| 145 | + |
| 146 | +Second, rerun your application with ``cabal run``, which also automatically rebuilds your application: |
| 147 | + |
| 148 | +.. code-block:: console |
| 149 | +
|
| 150 | + $ cabal run my-app -- +RTS -pj -RTS |
| 151 | + Resolving dependencies... |
| 152 | + Build profile: -w ghc-9.10.1 -O1 |
| 153 | + In order, the following will be built (use -v for more details): |
| 154 | + - base64-bytestring-1.2.1.0 (lib) --enable-profiling (requires build) |
| 155 | + - cryptohash-sha256-0.11.102.1 (lib) --enable-profiling (requires build) |
| 156 | + ... |
| 157 | + <app runs and finishes> |
| 158 | +
|
| 159 | +You can now find profiling data of dependencies in the report ``my-app.prof`` |
| 160 | +to analyze. More information on how to configure Cabal options can be found in the |
| 161 | +:ref:`Cabal options sections <package-configuration-options>`. |
0 commit comments