Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

slow byte code with tail recursion

Inspired by the answer to this SO question I took the code to check an imperative loop against tail recursion:

let rec nothingfunc i =
  match i with
  | 1000000000 -> 1
  | _ -> nothingfunc (i+1)

let nothingloop1 () =
  let i = ref 0 in
   while !i < 1000000000 do incr i done;
   1

let timeit f v =
  let t1 = Unix.gettimeofday() in
  let _ = f v in
  let t2 =  Unix.gettimeofday() in
    t2 -. t1

let () =
  Printf.printf "recursive function: %g s\n%!" (timeit nothingfunc 0);
  Printf.printf "while loop with ref counter buitin incr: %g s\n%!" (timeit nothingloop1 ());

For bytecode and native code the results are

str@s131-intel:~> ./bench_loop
recursive function: 20.7656 s
while loop with ref counter buitin incr: 12.0642 s
str@s131-intel:~> ./bench_loop.opt 
recursive function: 0.755594 s
while loop with ref counter buitin incr: 0.753947 s

The question is: what is the reason for the big difference 20 to 12 seconds execution time?

Edit, my conclusion:

A function call apply (in byte code) involves a stack size check, possible stack enlargement, and a check for signals. For maximum performance the native code compiler will deliver.

(Side note: asking here on SO because it is search engine friendly.)

like image 842
Str. Avatar asked Sep 27 '22 08:09

Str.


1 Answers

look at the output of ocamlfind ocamlc -package unix test.ml -dlambda

(nothingloop1/1010 =
     (function param/1022
       (let (i/1011 =v 0)
         (seq (while (< i/1011 100000000) (assign i/1011 (1+ i/1011))) 1)))

(nothingfunc/1008
   (function i/1009
     (if (!= i/1009 100000000) (apply nothingfunc/1008 (+ i/1009 1)) 1)))

So apparently assign is faster than apply. There seems to be checks for stack overflows and signals at function invocations, but not for a simple assign. For details, you have to look at: https://github.com/ocaml/ocaml/blob/trunk/byterun/interp.c

like image 152
rafix Avatar answered Nov 10 '22 22:11

rafix