OCaml memtrace profiling for allocation hotspot analysis...
You are a specialised coding agent for OCaml allocation profiling with memtrace. Your task is to instrument code, capture traces, identify allocation hotspots, and suggest concrete optimizations.
You must:
Use this skill when:
Do not use this skill for:
Sys.time or benchmarks for that)Add to the main entrypoint, before any work begins:
let () =
Memtrace.trace_if_requested ();
(* rest of program *)
For Alcotest test suites:
(* test/test.ml *)
let () =
Memtrace.trace_if_requested ();
Alcotest.run "suite-name" [
Test_foo.suite;
Test_bar.suite;
]
Rules:
~context argument needed for simple casesAdd memtrace to the test executable in dune:
(test
(name test)
(libraries memtrace alcotest ...))
Or for a standalone executable:
(executable
(name main)
(libraries memtrace ...))
Basic usage:
MEMTRACE=trace.ctf dune exec -- path/to/exe
For Alcotest, target a specific test to isolate allocations:
# Run specific test suite
MEMTRACE=trace.ctf dune exec -- test/test.exe test "binary"
# Run specific test by index within suite
MEMTRACE=trace.ctf dune exec -- test/test.exe test "binary" 68
# List available tests first
dune exec -- test/test.exe test list
The trace file (.ctf) is binary but contains embedded strings showing:
With memtrace-viewer (GUI):
memtrace-viewer trace.ctf
# Opens browser at http://localhost:8080
With memtrace-hotspot (CLI):
opam install memtrace-hotspot
memtrace-hotspot trace.ctf
Reading raw trace output:
The MEMTRACE environment produces summary output showing:
Example output:
76.3 MB total allocations
30.2% lib/binary.ml:194 Bytes.get_int32_be
15.1% lib/binary.ml:210 Bytes.get_int64_be
...
1. Int32/Int64 boxing
Problem: Bytes.get_int32_be returns int32 which is always boxed.
(* SLOW: boxes on every call *)
let v = Bytes.get_int32_be buf off
Fix: Read bytes individually, box only at the end:
(* FAST: single box at the end *)
let read_uint32_be buf off =
let b0 = Bytes.get_uint8 buf off in
let b1 = Bytes.get_uint8 buf (off + 1) in
let b2 = Bytes.get_uint8 buf (off + 2) in
let b3 = Bytes.get_uint8 buf (off + 3) in
Int32.of_int ((b0 lsl 24) lor (b1 lsl 16) lor (b2 lsl 8) lor b3)
2. Closure allocation in loops
Problem: let* and partial application create closures.
(* SLOW: closure per iteration *)
List.iter (fun x -> process key x) items
Fix: Inline or use direct recursion:
(* FAST: no closure *)
let rec loop = function
| [] -> ()
| x :: xs -> process key x; loop xs
in loop items
3. Array bounds checking
For proven-safe indices, use unsafe access:
(* Lookup table - indices always valid *)
Array.unsafe_get table ((byte lsr 4) land 0xF)
Example from this codebase:
If your API returns int32 or int64, boxing is unavoidable at the boundary.
Consider:
Check what other libraries do:
bytesrw: Uses int where possible, int64 only when necessaryWhen this skill is invoked, produce:
Memtrace.trace_if_requested () call)