Skip to content

perf profiling in 4.14 and 5.0: calls to GC misattributed? #12563

Description

@panglesd

I wanted to profile some programs using perf, and I observed some strange behaviour on OCaml 5. Switching to OCaml 4, the number seemed more reasonable.

I would tend to say that the reason is that time spent in the GC is not put as a child of the correct function perf since 5.0, seeing the output below.

Here is a somewhat minimal example:

(* test.ml *)

let l = ref []

let rec lots_of_allocs i =
  if i = 0 then ()
  else (
    for _ = 0 to 99999 do
      let i = ref 0 in
      i := !i + 1;
      i := !i - 1;
      l := !i :: !l
    done;
    l := [];
    lots_of_allocs (i - 1))

let rec lots_of_nothing i =
  if i = 0 then ()
  else (
    for _ = 0 to 100000000 do
      ()
    done;
    lots_of_nothing (i - 1))

let measure f =
  let t =
    let start = Unix.gettimeofday () in
    let _ = f () in
    let end_ = Unix.gettimeofday () in
    end_ -. start
  in
  Format.printf "Took %f seconds.\n%!" t

let () =
  print_endline "Starting lots_of_nothing";
  (measure @@ fun () -> lots_of_nothing 100);
  print_endline "Starting a lots_of_allocs";
  (measure @@ fun () -> lots_of_allocs 1000);
  print_endline "finished"

When I run this file, I have some information about the time spent in each function, lots_of_allocs and lots_of_nothing:

$ ocamlopt --version
5.0.0
$ ocamlopt -I +unix -g unix.cmxa test.ml
$ perf record --call-graph dwarf ./a.out 
Starting lots_of_nothing
Took 2.361002 seconds.
Starting a lots_of_allocs
Took 1.551887 seconds.
finished
[ perf record: Woken up 538 times to write data ]
[ perf record: Captured and wrote 134.438 MB perf.data (16701 samples) ]

However, if I ask perf about the time spent in each function, I get a result which does not match the measured time:

$ perf report
[...]
-   64.97%     0.00%  a.out    a.out                 [.] Test.entry                       
     Test.entry                                                                           
   - Test.measure_276                                                                     
        61.30% Test.lots_of_nothing_273                                                   
        3.67% Test.lots_of_allocs_269  
[...]

So, only 3.67% on lots_of_allocs, while it takes 1.55 seconds (compared to 61.30% for the 2.3 seconds of lots_of_nothing).


After that, I tried the same thing using ocaml 4.14:

$ ocamlopt --version
4.14.1
$ ocamlopt -I +unix -g unix.cmxa test.ml
$ perf record --call-graph dwarf ./a.out
Starting lots_of_nothing
Took 2.540385 seconds.
Starting a lots_of_allocs
Took 2.139729 seconds.
finished
[ perf record: Woken up 596 times to write data ]
[ perf record: Captured and wrote 149.053 MB perf.data (18499 samples) ]

Then, perf reports:

$ perf report
[...]
-   94.31%     0.00%  a.out    a.out                 [.] Test.entry                       
     Test.entry                                                                           
   - Test.measure_275                                                                     
        59.78% Test.lots_of_nothing_272                                                   
      - 34.53% Test.lots_of_allocs_268                                                    
         + 29.47% caml_call_gc                                                            
           2.30% caml_modify 
[...]

The time is now correctly counted, and there are children to the lots_of_allocs function. One of them is the gc, and seems to account for the missing time in OCaml 5...

Metadata

Metadata

Assignees

No one assigned

    Labels

    PerformancePR or issues affecting runtime performance of the compiled programs

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions