Performance, Profiling and Benchmarking

47.1 Benchmarking with criterion / tasty-bench

import Criterion.Main

fibList :: Int -> Integer
fibList n = fibs !! n where fibs = 0 : 1 : zipWith (+) fibs (tail fibs)

main :: IO ()
main = defaultMain
  [ bgroup "fib"
      [ bench "naive 25" $ whnf fibNaive 25
      , bench "list 25" $ whnf fibList 25
      , bench "list 10000" $ nf fibList 10000
      ]
  ]
  where fibNaive n = if n < 2 then toInteger n else fibNaive (n - 1) + fibNaive (n - 2)

47.2 Profiling

Tools: eventlog2html, ghc-debug (heap analysis), ThreadScope, profiteur (visualize .prof), ghc-prof-flamegraph.

cabal build --enable-profiling --ghc-options="-fprof-auto"
cabal run my-app --enable-profiling -- +RTS -p -RTS        # time/allocation profile → my-app.prof
cabal run my-app --enable-profiling -- +RTS -hc -RTS       # heap profile by cost centre → .hp
hp2ps -c my-app.hp                                         # or use eventlog2html for interactive charts
./my-app +RTS -s                                           # GC statistics, total allocation, max residency
ghc -O2 -ddump-simpl -dsuppress-all Main.hs                # inspect the optimized Core

47.3 Optimization Techniques

Compile with -O2 Enables more optimizations (specialization, inlining, worker/wrapper)

foldl', bang patterns, strict fields, Data.Map.Strict, avoid lazy accumulators

{-# UNPACK #-} !Double fields, unboxed vectors, -funbox-strict-fields

Text/ByteString instead of String, Vector/IntMap instead of lists/Map where appropriate

List and vector fusion eliminate intermediate structures (sum . map f compiles to a loop)

{-# SPECIALIZE #-}, {-# INLINABLE #-} for polymorphic functions used at known types INLINE pragmas Inline small hot functions (carefully) Avoid String concatenation and nub Use Builder, Set -threaded +RTS -N, parMap, async

+RTS -A64m (larger allocation area), --nonmoving-gc for low latency, -qg Mutable code in ST/IO

Mutable vectors/arrays for hot inner loops Profile, don't guess Allocation is often the bottleneck