Dear All,
I have the following two tests, with the test output in comments after each test:
let test2 () =
Printf.printf "Test 2\n%!";
let x = ref "hello" in
let _ = Gc.finalise (fun _ -> Printf.printf "x has been GC'ed\n") x in
let _ = Gc.full_major () in
let _ = Printf.printf "This should be printed after the 'x has been GC'ed' message\n" in
()
(*
Test 2
x has been GC'ed
This should be printed after the 'x has been GC'ed' message
*)
let test3 () =
let open struct
let _ = Printf.printf "Test 3\n%!"
let x = ref "hello"
let _ = Gc.finalise (fun _ -> Printf.printf "x has been GC'ed\n") x
let _ = Gc.full_major ()
let _ = Printf.printf "This should be printed after the 'x has been GC'ed' message\n"
end
in
()
(*
Test 3
This should be printed after the 'x has been GC'ed' message
NOTE: the finaliser is not run, and x is not GC'ed
*)
For test3, I naively expected it might behave the same as test2. But it doesn’t (the heap value x is not GC’ed by the full_major). Can someone explain what is happening? Thanks
1 Like
I guess one possible interpretation is: all values in a structure are reachable during the structure initialization phase (i.e., when the toplevel bindings in the structure are evaluated).
Can someone confirm this is correct (if it is)?
I don’t observe any difference between your two tests (with a flambda-enable compiler)?
Notably, even your second test seems to depend on some native backend optimization: it fails for me with the bytecode compiler.
I assume that you’re using the native compiler (in bytecode, let-bound variables are kept alive as long as they’re in scope).
The difference between the two tests is that struct (* ... *) let x = ref "hello" (* ... *) end
evaluates to a structure that contains x
. So x
needs to stay alive for the whole duration of the structure.
But you can get around it by constraining the signature of your module so that it does not include x
:
let test3 () =
let open (struct
let _ = Printf.printf "Test 3\n%!"
let x = ref "hello"
let _ = Gc.finalise (fun _ -> Printf.printf "x has been GC'ed\n") x
let _ = Gc.full_major ()
let _ = Printf.printf "This should be printed after the 'x has been GC'ed' message\n"
end : sig end)
in
()
This should give you the same results as test2
.
Flambda is better at removing unused allocations. In this case the module being opened is not used anywhere, so Flambda will remove the corresponding allocation. Without this allocation, nothing keeps x
alive so it can be collected during the Gc.full_major
call.
3 Likes
Yes indeed, I was using the native compiler. Your explanation makes sense, and highlights that there may be differences in behaviour with Flambda, in these edge cases. Thanks very much!