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 Core47.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