如何在 OCaml 中经过一定时间后有效地停止函数

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

最近,我想优化我的 OCaml 程序。有人建议我做一个 perf 分析。

好吧,当我看到 Sys.time() 是在我的程序中花费最多时间的函数时,我感到很惊讶,因为我在每个循环中都调用它。

我会尽量减少调用它的次数,但我想知道,是否存在一种更有效的方法来强制程序在一定时间后 return?

也许比 Sys.time 更有效地获取当前处理时间,或者完全不同的东西。

看起来您 运行 在 Windows 系统上。我无法访问 Windows,但在我的 Mac 上,Unix.gettimeofdaySys.time 快得多。它 returns 挂钟时间而不是处理时间,但实际上我通常想要挂钟时间。我很少对在代码上花费的 CPU 个周期的具体数量感兴趣。

这是我 运行 在 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)

输出:

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

在 Linux 环境中,有类似的结果(由@Butanium 贡献):

Sys.time 4.097320
Unix.gettimeofday 0.508475

这是我在 Ubuntu 18.04.4

的结果
Sys.time 6.156210
Unix.gettimeofday 0.309533

(这是一个装饰有头骨图片的小型 NUC 系统。)

总结一下,不同系统的时间不同,但是gettimeofday要快很多。

您可以按照 Jeffrey 的建议使用更高效的 Unix.gettimeofday 函数,但除此之外,您还可以尝试进行更少的比较。

首先,你可以每千次(或更多,或更少,选择正确的数字)迭代检查时间,例如,

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

或者,如果您的代码进行了分配,并且您并不真正在意超时的确切时间(即,您可以等待比所选超时时间长一点的时间,但希望 运行 您的函数位于至少指定的时间),然后您可以设置闹钟。它会检查每个主要的集合,如果你的代码没有分配很多1),它可能需要一些时间才能触发,例如

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

另一个适用于 Unix 系统且仍然需要在循环内部分配的选项是依赖于 Unix 警报信号。这是性能最高的选项,但存在一些问题,例如便携性和可靠性(如果其他人决定使用警报信号,它将无法工作)。

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) 这个例子并没有分配多少东西,而且在几十秒的超时情况下也能正常工作。如果超时小于 10 秒,它会比指定的持续时间长 运行s,因为在第一个主要收集开始之前通常需要几秒钟。