|
| 1 | +# Haskell Performance Analysis |
| 2 | + |
| 3 | +## GHC RTS Stats |
| 4 | + |
| 5 | +RTS Stats give entire OS process level (not OS thread level) cpu time |
| 6 | +and not Haskell thread cpu time. When multiple OS threads are used, the |
| 7 | +cpu time recorded is the cpu time of all the threads combined. Also, the |
| 8 | +way kernel accounts this time it could be off by a little (microseconds) |
| 9 | +because each thread's cpu time is recorded at the last accounting |
| 10 | +event. Allocations are recorded by the GHC RTS only at the GC boundary, |
| 11 | +so the allocations reported are from the point when the last GC |
| 12 | +happened. So we need to be careful when using or interpreting these |
| 13 | +stats. |
| 14 | + |
| 15 | +If we built the program without -threaded and we are using a single |
| 16 | +Haskell thread then we can get cpu time between any two points in the |
| 17 | +program accurately. Accurate accounting of allocations will require a GC |
| 18 | +to be forced which is not usually practical. |
| 19 | + |
| 20 | +In a multithreaded program using RTS stats we can only tell time how |
| 21 | +much total CPU time (and allocations) the entire Haskell process (all |
| 22 | +threads) spent between two points, but we cannot tell which Haskell |
| 23 | +thread spent how much time. |
| 24 | + |
| 25 | +## GHC Event logging |
| 26 | + |
| 27 | +Eventlog based Haskell thread aware time and allocation analysis is |
| 28 | +possible with stock GHC but there are some limitations and drawbacks |
| 29 | +which are fixed in the RTS patch described below. The patch basically |
| 30 | +adds accurate information and more information, and we then use a custom |
| 31 | +event log analysis program to provide an accurate and comprehensive |
| 32 | +picture of the entire program. |
| 33 | + |
| 34 | +TBD: document the exact limitations and differences. |
| 35 | + |
| 36 | +## threadCPUTime# prim op |
| 37 | + |
| 38 | +Available in the |
| 39 | +[GHC 9.2.8 RTS patch](https://github.com/composewell/ghc/releases/tag/ghc-9.2.8-perf-counters-1-rc1). |
| 40 | + |
| 41 | +Install the patched GHC using: |
| 42 | + |
| 43 | +``` |
| 44 | +ghcup install -u https://github.com/composewell/ghc/releases/download/ghc-9.2.8-perf-counters-1-rc1/ghc-9.2.8.20231130-x86_64-unknown-linux.tar.xz ghc |
| 45 | +``` |
| 46 | + |
| 47 | +This is a very simple and easy to use mechanism. The RTS is modified |
| 48 | +such that we record the accurate time and allocation information in a |
| 49 | +Haskell thread control block at the points when the thread is scheduled |
| 50 | +and descheduled. Thus for each Haskell thread we can always get how much |
| 51 | +time the thread spent on CPU and how much allocations it did. |
| 52 | + |
| 53 | +An RTS API is provided to fetch the current thread's accumulated cpu |
| 54 | +time and allocation stats. We can collect these stats between point A |
| 55 | +and B in a program, diff will tell us the time spent and allocations |
| 56 | +between the two points. |
| 57 | + |
| 58 | +We have to ensure that we are diffing the data for the same thread id at |
| 59 | +both the points. See [this example program](./threadCPUTime.hs). |
| 60 | + |
| 61 | +The API has some measurement overhead but it is not very high. If we |
| 62 | +are nesting measurements be aware that outer measurement will measure |
| 63 | +the measurement overhead of the inner one. If you are measuring a |
| 64 | +relatively small amount of time then reduce the overhead (approx 2 |
| 65 | +microseconds and 300 byte allocations, measure the exact value using an |
| 66 | +empty code block). |
| 67 | + |
| 68 | +This is very useful in micro-measurements and analysis of the CPU cost |
| 69 | +different segments of code in a particular Haskell thread without worrying |
| 70 | +about the preemption points of the thread. |
| 71 | + |
| 72 | +By measuring the wall clock time as well at the two points we can find |
| 73 | +the idle time for the thread. However, the idle time includes the queue |
| 74 | +time and the IO time - it may not be very useful unless we know the |
| 75 | +breakup. For that we need to add the facility to measure either the |
| 76 | +queue time or the IO time. |
| 77 | + |
| 78 | +Limitations: this allows only thread specific measurements, we cannot |
| 79 | +tell what other threads and everything else in the system is doing |
| 80 | +between the two points of measurements. It can be added to the patch |
| 81 | +though. For accurate synchronization (if needed) of all threads at the |
| 82 | +given points we can stop-the-world, can be useful in testing but not a |
| 83 | +good idea in production though. Also, managing windows with possible |
| 84 | +nesting can complicate the RTS code. |
| 85 | + |
| 86 | +## Eventlog based perf counters |
| 87 | + |
| 88 | +Available in GHC 8.10.7 RTS patch. Can be ported to later GHCs. |
| 89 | + |
| 90 | +This gives you a more comprehensive picture of the entire program |
| 91 | +between any two specified points, it gives a detailed report about all |
| 92 | +the threads in the system not just the current thread. |
| 93 | + |
| 94 | +See the [README](../README.md) for more details on this. |
0 commit comments