;;; -*- Mode: Lisp -*- ;;; ;;; $Header: /home/gene/library/website/docsrc/lh/RCS/profile.lisp,v 395.1 2008/04/20 17:25:45 gene Exp $ ;;; ;;; A small profiler for Lisp. Copied from ;;; "Paradigms of Artificial Intelligence: Case ;;; Studies in Common Lisp", by Peter Norvig. ;;; (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." (setf (get fn-name 'profiled-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) (defvar *profiled-functions* nil) (defmacro profile (&rest fn-names) "Profile fn-names. With no args, list profiled functions." `(mapcar #'profile1 (setf *profiled-functions* (union *profiled-functions* ',fn-names)))) (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 get-fast-time () "Return the elapsed time. This may wrap around; use FAST-TIME-DIFFERENCE to compare." #+Explorer (time:microsecond-time) ; do this on an Explorer #-Explorer (get-internal-real-time)) ; do this on a non-Explorer (defun fast-time-difference (end start) "Subtract two time points." #+Explorer (time:microsecond-time-difference end start) #-Explorer (- end start)) (defun fast-time->seconds (time) "Convert a fast-time interval into seconds." #+Explorer (/ time 1000000.0) #-Explorer (/ time internal-time-units-per-second)) (proclaim '(inline profile-enter profile-exit inc-profile-time)) (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)))) (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)))) ;; I could not find this function definition in the ;; chapter in PAIP. It's the only function I have ;; written myself & not copied from PAIP. (defun profile-count (fn-name) (get fn-name 'profile-count)) (defun profile-time (fn-name) (get fn-name 'profile-time)) (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: ~,2F seconds." (fast-time->seconds total-time)) (format t "~& Count Secs Time% Name") ;; I added the sort so that the functions which took the ;; most time will preceed those that took the least. ;; To work exactly like this function from PAIP, you'd ;; replace the entire SORT form with "fn-names". (loop for name in (sort fn-names #'> :key #'profile-time) do (format t "~&~7D ~6,2F ~3D% ~A" (profile-count name) (fast-time->seconds (profile-time name)) (round (/ (profile-time name) total-time) 0.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)))) ;;; --- end of file ---