Butanium
Butanium

Reputation: 790

How to efficiently stop a function after a certain amount of time in OCaml

Lately, I wanted to optimize my OCaml program. Someone suggested me to do a perf analysis.

And well, I was surprised when I saw that Sys.time() is the function which takes the biggest amount of time during my program, because I call it every loop. enter image description here

I'll try to call it less often, but I was wondering, does it exist a more efficient way to force a program to return after a certain amount of time ?

Maybe something more efficient than Sys.time to get the current processing time, or something completely different.

Upvotes: 0

Views: 430

Answers (2)

Jeffrey Scofield
Jeffrey Scofield

Reputation: 66823

It looks like you're running on a Windows system. I don't have access to Windows, but on my Mac, Unix.gettimeofday is much faster than Sys.time. It returns the wall clock time rather than processing time, but in fact I usually want wall clock time. I very rarely am interested in the specific number of CPU cycles that have been spent on the code.

Here's a test program I ran on my Mac:

let start_time = Unix.gettimeofday () in
for i = 1 to 10000000 do
    ignore (Sys.time ())
done;
let end_time = Unix.gettimeofday () in
Printf.printf "Sys.time %f\n" (end_time -. start_time);

let start_time = Unix.gettimeofday () in
for i = 1 to 10000000 do
    ignore (Unix.gettimeofday ())
done;
let end_time = Unix.gettimeofday () in
Printf.printf "Unix.gettimeofday %f\n" (end_time -. start_time)

Output:

$ ./m
Sys.time 4.060067
Unix.gettimeofday 0.320934

In a Linux environment, there are similar results (contributed by @Butanium):

Sys.time 4.097320
Unix.gettimeofday 0.508475

Here are my results from Ubuntu 18.04.4

Sys.time 6.156210
Unix.gettimeofday 0.309533

(This is a small NUC system adorned with a picture of a skull.)

The summary is that the times are different on different systems but gettimeofday is a lot faster.

Upvotes: 2

ivg
ivg

Reputation: 35270

You can use a more efficient Unix.gettimeofday function as Jeffrey suggested, but in addition to that, you can also try to make fewer comparisons.

First of all, you can check the time only every thousand (or more, or less, pick the right number) iterations, e.g.,

if i mod 10000 = 0 && check_timeout ()
then do_work ()
else raise Timeout

Alternatively, if your code allocates and you are not really bothered with exact times in your timeout, (i.e., you can wait a bit more than the selected timeout, but would like to run your function at least the specified amount of time), then you can set an alarm. It will be checked every major collection, and if your code doesn't allocate a lot1), it might take some time before it triggers, e.g.,

exception Timeout

let rec infinite () =
  let x = Random.int64 0xDEADBEEFL in
  Format.printf "%Ld\n" x;
  infinite ()

let max_time = 60.

let () =
  let start = Sys.time () in
  let alarm = Gc.create_alarm (fun () ->
      if Sys.time () -. start > max_time
      then raise Timeout) in
  let () = try infinite () with Timeout -> () in
  Gc.delete_alarm alarm

Another option that works on Unix systems and still requires allocation inside of the loop, is to rely on the Unix alarm signal. This is the most performant option, but it suffers from a few problems, like portability and reliability (it won't work if someone else will decide to use the alarm signal).

let timeout = ref false

let rec infinite () =
  if not timeout.contents then infinite  ()

let () =
  let stopit = Sys.Signal_handle (fun _ -> timeout := true) in
  Sys.set_signal Sys.sigalrm stopit;
  ignore (Unix.alarm 10);
  infinite ();

1) This example doesn't allocate much and it works fine with timeouts that are a matter of dozens of seconds. If the timeout is less than 10 seconds, it runs more than the specified duration as it usually takes a few seconds before the first major collection kicks in.

Upvotes: 2

Related Questions