This question is sort-of a follow-up question to this SO question: How to make a covariant observable in OCaml
The accepted answer's author has (casually) noted that using let rec
to bind two independent values would be more "economical" than two separate let
bindings.
let make x =
let queue = Queue.create () in
let obj = x in
let watch f = Queue.add f queue in
let notify () = Queue.iter (fun f -> f x) queue in
{ obj; watch; notify; }
vs
let make x =
let queue = Queue.create () in
let obj = x in
let rec watch f = Queue.add f queue
and notify () = Queue.iter (fun f -> f x) queue in
{ obj; watch; notify; }
Was his statement correct? If so, why is the second version more "economical"?
As I stated already in my comment, it seems that by using let rec
, you avoid creating more closures. To inspect this, I created two slightly different files shown here:
test1.ml
has the "usual" way without let rec
:
let test1 x =
let x = 5 in
let w () = x + 1 in
let n () = x + 1 in
w () + n ()
On the other hand, test2.ml
uses let rec
:
let test2 x =
let x = 5 in
let rec w () = x + 1
and n () = x + 1 in
w () + n ()
I then ocamlc -dinstr
'd both files (i.e. I generated bytecode for both files) and obtained the following:
For test1.ml
, I got:
branch L2
L3: envacc 1
offsetint 1
return 1
L4: envacc 1
offsetint 1
return 1
L1: const 5
push
acc 0
closure L4, 1
push
acc 1
closure L3, 1
push
const 0a
push
acc 1
apply 1
push
const 0a
push
acc 3
apply 1
addint
return 4
L2: closure L1, 0
push
acc 0
makeblock 1, 0
pop 1
setglobal Closuretest!
The file test2.ml
resulted in the following:
branch L2
L3: envacc 3
offsetint 1
return 1
L4: envacc 1
offsetint 1
return 1
L1: const 5
push
acc 0
closurerec 3 4, 1
const 0a
push
acc 1
apply 1
push
const 0a
push
acc 3
apply 1
addint
return 4
L2: closure L1, 0
push
acc 0
makeblock 1, 0
pop 1
setglobal Closuretest2!
From a Caml Virtual Machine — Instruction set Document (unfortunately, I don't know if it even is the official one, but it looks reasonable), it seems that the instructions closure
and closurerec
generate a closure on the stack. As you can see, the bytecode for test1.ml
generates 3 closures in total, while test2.ml
only generates two closures (one via closurerec
).
I am no assembly guru, but you can test ocamlopt -S filename.ml
, so that the compiler leaves (and does not delete) the assembly (then in filename.s
), where you can possibly spot analogous differences, as well.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With