;;; The profile utility, based on Norvig's "Paradigms of Artificial
;;; Intelligence programming".
;;; Adapted for XLisp by Leo Sarasua (modifications marked LSG)
#+:packages
(unless (find-package "TOOLS")
(make-package "TOOLS" :use '("XLISP")))
(in-package "TOOLS")
(export '(profile unprofile profile-report with-profiling))
(defmacro profile (&rest fn-names)
"Profile fn-names. With no args, list profiled functions."
`(mapcar #'profile1
(setf *profiled-functions*
(union *profiled-functions*
(remove-if-not #'fboundp ',fn-names) )))) ; LSG
(defmacro unprofile (&rest fn-names)
"Stop profiling fn-names. With no args, stop all profiling."
`(progn
(mapcar #'unprofile1
,(if fn-names `',fn-names *profiled-functions*) )
(setf *profiled-functions*
,(if (null fn-names)
nil
`(set-difference *profiled-functions*
',fn-names )))))
(defun profile1 (fn-name)
"Make the function count how often it is called"
;; First save away the old, unprofiled function.
;; Then make the name be a new function that increments
;; a counter and then calls the original function.
(let ((fn (symbol-function fn-name)))
(unless (eq fn (get fn-name 'profiled-fn))
(let ((new-fn (profiled-fn fn-name fn)))
(setf (symbol-function fn-name) new-fn
(get fn-name 'profiled-fn) new-fn
(get fn-name 'unprofiled-fn) fn
(get fn-name 'profile-time) 0
(get fn-name 'profile-count) 0 ))))
fn-name )
(defun unprofile1 (fn-name)
"Make the function stop counting how often it is called"
(when (fboundp fn-name) ; LSG
(setf (get fn-name 'profile-time) 0)
(setf (get fn-name 'profile-count) 0)
(when (eq (symbol-function fn-name) (get fn-name 'profiled-fn))
;; normal case: restore unprofiled version
(setf (symbol-function fn-name)
(get fn-name 'unprofiled-fn) ))
fn-name ))
(defun profile-report (&optional
(fn-names (copy-list *profiled-functions*))
(key #'profile-count))
"Report profiling statistics on given functions."
(let ((total-time (reduce #'+ (mapcar #'profile-time fn-names))))
(unless (null key)
(setf fn-names (sort fn-names #'> :key key)) )
(format t "~&Total elapsed time: ~d seconds."
(fast-time->seconds total-time) )
(format t "~& Count Secs Time% Name")
(dolist (name fn-names)
(format t "~&~7D ~6,2F ~3d% ~A"
(profile-count name)
(fast-time->seconds (profile-time name))
(if (< total-time 1e-9)
0
(round (/ (profile-time name) total-time) .01) )
name ))))
(defmacro with-profiling (fn-names &rest body)
`(progn
(unprofile . ,fn-names)
(profile . ,fn-names)
(setf *profile-call-stack* nil)
(unwind-protect
(progn . ,body)
(profile-report ',fn-names)
(unprofile . ,fn-names) )))
(defun profiled-fn (fn-name fn)
"Return a function that increments the count, and times."
#'(lambda (&rest args)
(profile-enter fn-name)
(multiple-value-prog1
(apply fn args)
(profile-exit fn-name) )))
(defun profile-count (fn-name) (get fn-name 'profile-count))
(defun profile-time (fn-name) (get fn-name 'profile-time))
(defvar *profiled-functions* nil
"Function names that are currently profiled" )
(defvar *profile-call-stack* nil)
(defun profile-enter (fn-name)
(incf (get fn-name 'profile-count))
(unless (null *profile-call-stack*)
;; Time charged against the calling function:
(inc-profile-time (first *profile-call-stack*)
(car (first *profile-call-stack*)) ))
;; Put a new entry on the stack
(push (cons fn-name (get-fast-time))
*profile-call-stack* ))
(defun profile-exit (fn-name)
;; Time charged against the current function:
(inc-profile-time (pop *profile-call-stack*)
fn-name )
;; Change the top entry to reflect current time
(unless (null *profile-call-stack*)
(setf (cdr (first *profile-call-stack*))
(get-fast-time) )))
(defun inc-profile-time (entry fn-name)
(incf (get fn-name 'profile-time)
(fast-time-difference (get-fast-time) (cdr entry)) ))
(defun fast-time->seconds (time)
"Convert a fast time interval into seconds"
(float (/ time internal-time-units-per-second)) )
(defun get-fast-time ()
"Return the elapsed time. This may wrap around;
use FAST-TIME-DIFFERENCE to compare."
(get-internal-real-time) )
(defun fast-time-difference (end start)
"Subtract two time points."
(- end start) )
syntax highlighted by Code2HTML, v. 0.9.1