Profiling in OCaml?

Hey there. I’m writing some functions to solve the Klotski Puzzle from the OCaml MOOC that was offered a couple years ago. Everything type checks and compiles, which is great news, BUT the functions produce a Stack overflow for all puzzles except the simplest ones. So obviously one or more of my functions are causing a bottleneck or so-called “hot spot”. I know that OCaml comes with some profiling tools, but the online documentation is kind of confusing. I just have a regular .ml file. I’m not using any special modules or anything like that. (Well, not entirely true. My .ml file calls some commands from the Graphics library. I just load that into utop when I need it.) Can someone explain step-by-step with a simple example how I could profile my .ml file? I’m kind of confused about this, and it’s something I should do because I don’t know which functions are at the root of the problem. THANK YOU!!!

Best,

Doug.

The fact that you get a stack overflow should first make you think
"Do I have a recursive function that calls itself in a non-tail call?",
because that is a common cause of a stack overflow in OCaml. To find that out, you don’t really need a profiler. You could take a hard look at any recursive (let rec f ...) function definitions that contain a call of the form g (f x) within their body. Once you rewrite them to avoid inner nested calls like that, you should be fine.
Another thing that may help you understand how your code executes is the debugger. I.e. turn on debugging support when compiling the bytecode version of your program (e.g. in ocamlbuild, set the debug tag) and then run it with ocamldebug and step through it.
Finally, if you really want profiling, i.e. time spent in various functions, a simple option which has helped me in the past is landmarks .

1 Like

Hey there. My file is huge. I probably have several functions of the form you describe. You know… let rec f = g (f x). But what if g is itself tail-recursive? Would that help?

Thanks,

Douglas.

If you can port it to BuckleScript, the profiling/debug tools provided by vscode is superb with zero config

I don’t think it helps that g is tail-recursive. I think merlin can actually show you if a call is a tail call if you ask it for the type under the cursor, although when I tried it right now I did not get it to work. But by doing a search for let rec you should be able to quickly find all recursive functions in your source code – checking a handful of them should not be a problem?!

Does Bucklescript support tailcall optimisation with mutually recursive function now? If the answer is yes, you should update https://github.com/BuckleScript/bucklescript/issues/849 . Otherwise, advising to port OCaml code to bucklescript for debugging purpose when a stackoverflow is involved (let’s not even talk about profiling) sounds like a quite biased and unhelpful advice.

1 Like

Hi Douglas, OCaml includes a feature to warn if your rec function is not tail-recursive: https://caml.inria.fr/pub/docs/manual-ocaml-4.06/extn.html#sec260

“ocaml.tailcall” or “tailcall” can be applied to function application in order to check that the call is tailcall optimized. If it it not the case, a warning (51) is emitted.

E.g.

[@ocaml.tailcall] let rec f x = if x < 1 then x else 1 + tail (x - 2)

…should warn.

Btw @octachron, I don’t believe Bob was saying ‘port it to BuckleScript’, it was more ‘If you can port to BuckleScript’.

2 Likes

Another thing that could be helpful short of actually using the debugger is to turn on backtrace printing. In utop you can call
Printexc.record_backtrace true;;
and this should then spit out a long stack trace on the next exception. That way you should see at which point in the source you were piling up the call stack.

@yawaramin I think the annotation should be at the call site? I think it should be

let rec f x = if x < 1 then x else 1 + (f [@tailcall]) (x - 2)

at least on cursory testing it did not work before the let rec for me.

Right, I just tried a few variations and couldn’t get it to work. Would any experts be able to jump in here?

For me the version I pasted does work, i.e. a warning 53 is raised on compilation.