函数式语言的简单时序分析器

Posted

技术标签:

【中文标题】函数式语言的简单时序分析器【英文标题】:Simple timing profiler for functional languages 【发布时间】:2012-08-14 18:59:47 【问题描述】:

我需要一个简单的时序分析器来估计我的程序某些部分的运行时间(用 OCaml 编写,但我相信这可以适用于其他函数式语言),但我找不到一个非常简单的解决方案,类似于可以使用诸如timer.start/timer.stop 之类的函数以命令式语言编写代码。所以我尝试了一个使用惰性评估的方法,它可以很好地满足我的需要,但是我没有找到对这种方法的任何引用,所以我想知道这种方法有缺陷,或者是否有更简单的解决方案。

那么,问题是:你知道函数式语言(尤其是 OCaml)的类似实现吗?如果是这样,请告诉我,我想借用他们的一些想法来改进我的“穷人分析器”(我见过this question,但对我没有多大帮助)。据我所知,GHC 已经有办法收集时间信息,所以这对 Haskell 来说可能不是问题。

顺便说一句,我尝试按照 OCaml 手册 (17.4) 中的说明进行时序分析,但它对于我需要的东西来说太“低级”了:它在 C 函数级别提供了很多信息,这使得它更难准确评估 OCaml 代码的哪一部分是罪魁祸首。

下面是我在 OCaml 中的实现(请注意,每次我想测量时间时,我都需要添加“惰性”表达式,但同时我可以很好地控制我需要多少信息)。

open Unix (* for the timers *)

(** 'timers' associates keys (strings) to time counters, 
   to allow for multiple simultaneous measurements. *)
let timers : (string, (float * float)) Hashtbl.t = Hashtbl.create 1

(** starts the timer associated with key <name> *)
let timer_start (name : string) : unit =
  let now = Unix.times () in
  Hashtbl.replace timers name (now.tms_utime, now.tms_stime)

(** Returns time elapsed between the corresponding call to 
   timer_start and this call *)
let timer_stop (name : string) : float =
  try
    let now = Unix.times () in
    let t = Hashtbl.find timers name in
    (now.tms_utime -. fst t) +. (now.tms_stime -. snd t)
  with
    Not_found -> 0.0

(** Wrapper for the timer function using lazy evaluation *)
let time (s : string) (e : 'a Lazy.t) : 'a =
  timer_start s;
  let a = Lazy.force e in
  let t2 = timer_stop s in
  (* outputs timing information *)
  Printf.printf "TIMER,%s,%f\n" s t2; a


(** Example *)
let rec fibo n = 
  match n with
    | 0 -> 1
    | 1 -> 1
    | n' -> fibo (n - 1) + fibo (n - 2)

let main =
  let f = time "fibo" (lazy (fibo 42)) in
  Printf.printf "f = %d\n" f

【问题讨论】:

这对我来说很好(典型的穷人)。我已经编写了一些这样的系统,它们对我有用。据我所知,您实际上并不需要使用惰性设施。似乎您可以只传递一个 thunk (fun () -> expr),这可能会避免为惰性值分配然后丢弃一些额外的结构。 (AFAIK 一个惰性值相当于一个 lambda 和一个盒子。)但是我猜惰性表达式在符号上有点温和。 thunk 确实是最好的选择,我本来想避免它,但是当我不得不使用有点丑陋的“懒惰”包装器时,我没有考虑回到以前的解决方案.. . 谢谢! 【参考方案1】:

Unix.times 测量 CPU 时间,而不是挂钟时间。因此,这仅适用于将所有时间都花在 CPU 上的计算代码。而且BTW hashtbl 也不需要,即使是同时进行多个测量,只需在timer_start 中返回开始时间并在timer_stop 中减去它。

【讨论】:

确实,我已经重构了一些代码来简化它,我什至没有注意到哈希表不再需要了......【参考方案2】:

结合@Jeffrey_Scofield 和@ygrek 的想法,“最穷人的时序分析器”确实非常简单,几乎不需要提及,这可以解释为什么我没有找到它。所以我合并了他们的答案并制作了一个更简单的版本:

open Unix (* for the timers *)

(* Wrapper for the timer function using a "unit -> 'a" thunk *)
let time (s : string) (e : unit -> 'a) : 'a =
  let tstart = Unix.times () in
  let a = e () in
  let tend = Unix.times () in
  let delta = (tend.tms_utime -. tstart.tms_utime) +. 
              (tend.tms_stime -. tstart.tms_stime) in
  (* outputs timing information *)
  Printf.printf "TIMER,%s,%f\n" s delta; a

(* Example *)
let rec fibo n = 
  match n with
    | 0 -> 1
    | 1 -> 1
    | n' -> fibo (n - 1) + fibo (n - 2)

let main =
  let f = time "fibo" (fun () -> fibo 42) in
  Printf.printf "f = %d\n" f

【讨论】:

以上是关于函数式语言的简单时序分析器的主要内容,如果未能解决你的问题,请参考以下文章

智能合约与函数式编程语言(从零开始学区块链 181)

函数式编程语言时代已经来临

函数式程序设计入门讲义

什么是函数式语言?

函数式编程初窥F#

作为函数式编程语言的 Python