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!!!
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 .
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?
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.
“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’.
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.