Skip to content
Performance and Profiling
step 1/7

Reading — step 1 of 7

Learn

~3 min readGADTs, First-Class Modules, Effects

OCaml is fast — competitive with C in many benchmarks. But getting that performance requires understanding allocation, boxing, and the GC. Real World OCaml has a chapter on this.

OCaml's representation

  • Integers — unboxed, tagged (61-bit on 64-bit systems; reserve 1 bit for tag, 1 for sign on some operations)
  • Floats — boxed when stored in records, unless [@@unboxed] or in a float-only record
  • Tuples — heap-allocated as a block of pointers
  • Strings, arrays, records — heap-allocated
let x = 42                     (* int — unboxed *)
let f = 3.14                    (* float — usually boxed *)
let pair = (1, 2)               (* tuple — heap allocated *)

Float boxing

Floats in records and arrays are usually BOXED unless you use special types:

type point = { x : float; y : float }    (* x and y are BOXED individually *)

(* Better — float-only record gets unboxed: *)
type point = { mutable x : float; mutable y : float }    (* unboxed inline *)

(* Float arrays are special: *)
let arr = Array.make 100 0.0    (* float array — unboxed if FLAT *)

Garbage collection

OCaml has a fast generational GC:

  • Minor heap — small, fast collection for new allocations (~32MB default)
  • Major heap — long-lived objects; collected by mark-sweep

Minor GC is fast (couple ms). Major GC is when most pauses come from. For latency-critical apps, tune Gc.set parameters or run major GC explicitly during quiet periods.

(* Inspect GC stats: *)
let stats = Gc.stat ()
Printf.printf "minor words: %.0f\n" stats.minor_words
Printf.printf "major words: %.0f\n" stats.major_words
Printf.printf "compactions: %d\n" stats.compactions

Avoiding allocations

(* Allocates a new tuple per call: *)
let swap (x, y) = (y, x)

(* No allocation — uses unboxed locals: *)
let swap_ints a b = (b, a)   (* still allocates the tuple to return *)

For hot paths, prefer:

  • Function arguments over tuples (no packaging)
  • Records with strict field types
  • Bytes and Buffer for strings
  • Arrays for fixed-size collections

Profiling tools

  • time (built-in shell) — wall-clock
  • Unix.gettimeofday — finer in-program timing
  • Spacetime — memory profiling (older versions)
  • statmemprof — sampling memory profiler (newer)
  • landmarks — annotation-based profiler (library)
  • perf (Linux) — system-level profiling on the binary
  • OCaml 5+ Eventring — built-in tracing
(* Simple timing: *)
let time_it f x =
    let start = Unix.gettimeofday () in
    let result = f x in
    let elapsed = Unix.gettimeofday () -. start in
    Printf.eprintf "took %.3fs\n" elapsed;
    result

Hot-loop tips

  • Avoid allocating tuples in tight loops — use refs or extra args
  • Use Array.unsafe_get / Array.unsafe_set when you've already bounds-checked — skip checks in hot paths
  • Inlining — small functions are auto-inlined; mark with [@@inline] to encourage
  • Pattern-match exhaustiveness — the compiler optimizes well-typed matches
  • Closures — capture only what you need; large closures pin objects

Native compilation vs bytecode

ocamlfind ocamlopt ...    # native, fast (default for production)
ocamlfind ocamlc ...      # bytecode, smaller, portable

For performance, use ocamlopt (native compiler). Bytecode is mostly for debug builds or when native isn't available.

Multicore (OCaml 5+)

OCaml 5 (2022) added multicore support via Domains:

let d = Domain.spawn (fun () ->
    (* runs on another core *)
    heavy_computation ())

let result = Domain.join d

Domains share state but each has its own minor heap. For data parallelism, true parallel execution is now possible.

Common mistakes

  • Premature optimization — profile first. Most code is fast enough.
  • Float boxing in numeric records — use float-only records or arrays for hot paths.
  • Tuple allocation in tight loops — pass extra arguments instead.
  • Skipping ocamlopt — bytecode is 5-10x slower. Use native for production.
  • Long-lived references holding short-lived data — promotes to major heap, slows GC. Drop references when done.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…