diff options
| author | Stefan Monnier | 2021-03-17 19:04:28 -0400 |
|---|---|---|
| committer | Stefan Monnier | 2021-03-17 19:04:28 -0400 |
| commit | 82c3bd1e4a58f6fefcb7d69b6e04013bd86f54be (patch) | |
| tree | 183c0845052e3d8a51c80e429b6333471a251b36 | |
| parent | 3cd3f230c924e5a095f37ea9c6ea6f9f6c5cf473 (diff) | |
| download | emacs-82c3bd1e4a58f6fefcb7d69b6e04013bd86f54be.tar.gz emacs-82c3bd1e4a58f6fefcb7d69b6e04013bd86f54be.zip | |
* lisp/emacs-lisp/benchmark.el (benchmark-call): New function
(benchmark-run, benchmark-run-compiled, benchmark): Use it.
(benchmark--adaptive): New internal function.
| -rw-r--r-- | doc/lispref/debugging.texi | 3 | ||||
| -rw-r--r-- | etc/NEWS | 5 | ||||
| -rw-r--r-- | lisp/emacs-lisp/benchmark.el | 98 |
3 files changed, 74 insertions, 32 deletions
diff --git a/doc/lispref/debugging.texi b/doc/lispref/debugging.texi index 8e4b0ebfe96..de98d2206e2 100644 --- a/doc/lispref/debugging.texi +++ b/doc/lispref/debugging.texi | |||
| @@ -1041,7 +1041,8 @@ functions written in Lisp, it cannot profile Emacs primitives. | |||
| 1041 | @cindex @file{benchmark.el} | 1041 | @cindex @file{benchmark.el} |
| 1042 | @cindex benchmarking | 1042 | @cindex benchmarking |
| 1043 | You can measure the time it takes to evaluate individual Emacs Lisp | 1043 | You can measure the time it takes to evaluate individual Emacs Lisp |
| 1044 | forms using the @file{benchmark} library. See the macros | 1044 | forms using the @file{benchmark} library. See the function |
| 1045 | @code{benchmark-call} as well as the macros | ||
| 1045 | @code{benchmark-run}, @code{benchmark-run-compiled} and | 1046 | @code{benchmark-run}, @code{benchmark-run-compiled} and |
| 1046 | @code{benchmark-progn} in @file{benchmark.el}. You can also use the | 1047 | @code{benchmark-progn} in @file{benchmark.el}. You can also use the |
| 1047 | @code{benchmark} command for timing forms interactively. | 1048 | @code{benchmark} command for timing forms interactively. |
| @@ -389,6 +389,11 @@ major mode. | |||
| 389 | 389 | ||
| 390 | * Changes in Specialized Modes and Packages in Emacs 28.1 | 390 | * Changes in Specialized Modes and Packages in Emacs 28.1 |
| 391 | 391 | ||
| 392 | ** Benchmark | ||
| 393 | *** New function 'benchmark-call' to measure the execution time of a function. | ||
| 394 | Additionally, the number of repetitions can be expressed as a minimal duration | ||
| 395 | in seconds. | ||
| 396 | |||
| 392 | ** Macroexp | 397 | ** Macroexp |
| 393 | --- | 398 | --- |
| 394 | *** New function 'macroexp-file-name' to know the name of the current file. | 399 | *** New function 'macroexp-file-name' to know the name of the current file. |
diff --git a/lisp/emacs-lisp/benchmark.el b/lisp/emacs-lisp/benchmark.el index 2a3efbe5a1b..439d3bd363e 100644 --- a/lisp/emacs-lisp/benchmark.el +++ b/lisp/emacs-lisp/benchmark.el | |||
| @@ -31,6 +31,8 @@ | |||
| 31 | 31 | ||
| 32 | ;;; Code: | 32 | ;;; Code: |
| 33 | 33 | ||
| 34 | (eval-when-compile (require 'subr-x)) ;For `named-let'. | ||
| 35 | |||
| 34 | (defmacro benchmark-elapse (&rest forms) | 36 | (defmacro benchmark-elapse (&rest forms) |
| 35 | "Return the time in seconds elapsed for execution of FORMS." | 37 | "Return the time in seconds elapsed for execution of FORMS." |
| 36 | (declare (indent 0) (debug t)) | 38 | (declare (indent 0) (debug t)) |
| @@ -41,6 +43,61 @@ | |||
| 41 | (float-time (time-since ,t1))))) | 43 | (float-time (time-since ,t1))))) |
| 42 | 44 | ||
| 43 | ;;;###autoload | 45 | ;;;###autoload |
| 46 | (defun benchmark-call (func &optional repetitions) | ||
| 47 | "Measure the run time of calling FUNC a number REPETITIONS of times. | ||
| 48 | The result is a list (TIME GC GCTIME) | ||
| 49 | where TIME is the total time it took, in seconds. | ||
| 50 | GCTIME is the amount of time that was spent in the GC | ||
| 51 | and GC is the number of times the GC was called. | ||
| 52 | |||
| 53 | REPETITIONS can also be a floating point number, in which case it | ||
| 54 | specifies a minimum number of seconds that the benchmark execution | ||
| 55 | should take. In that case the return value is prepended with the | ||
| 56 | number of repetitions actually used." | ||
| 57 | (if (floatp repetitions) | ||
| 58 | (benchmark--adaptive func repetitions) | ||
| 59 | (unless repetitions (setq repetitions 1)) | ||
| 60 | (let ((gc gc-elapsed) | ||
| 61 | (gcs gcs-done) | ||
| 62 | (empty-func (lambda () 'empty-func))) | ||
| 63 | (list | ||
| 64 | (if (> repetitions 1) | ||
| 65 | (- (benchmark-elapse (dotimes (_ repetitions) (funcall func))) | ||
| 66 | (benchmark-elapse (dotimes (_ repetitions) (funcall empty-func)))) | ||
| 67 | (- (benchmark-elapse (funcall func)) | ||
| 68 | (benchmark-elapse (funcall empty-func)))) | ||
| 69 | (- gcs-done gcs) | ||
| 70 | (- gc-elapsed gc))))) | ||
| 71 | |||
| 72 | (defun benchmark--adaptive (func time) | ||
| 73 | "Measure the run time of FUNC, calling it enough times to last TIME seconds. | ||
| 74 | Result is (REPETITIONS . DATA) where DATA is as returned by `branchmark-call'." | ||
| 75 | (named-let loop ((repetitions 1) | ||
| 76 | (data (let ((x (list 0))) (setcdr x x) x))) | ||
| 77 | ;; (message "Running %d iteration" repetitions) | ||
| 78 | (let ((newdata (benchmark-call func repetitions))) | ||
| 79 | (if (<= (car newdata) 0) | ||
| 80 | ;; This can happen if we're unlucky, e.g. the process got preempted | ||
| 81 | ;; (or the GC ran) just during the empty-func loop. | ||
| 82 | ;; Just try again, hopefully this won't repeat itself. | ||
| 83 | (progn | ||
| 84 | ;; (message "Ignoring the %d iterations" repetitions) | ||
| 85 | (loop (* 2 repetitions) data)) | ||
| 86 | (let* ((sum (cl-mapcar #'+ data (cons repetitions newdata))) | ||
| 87 | (totaltime (nth 1 sum))) | ||
| 88 | (if (>= totaltime time) | ||
| 89 | sum | ||
| 90 | (let* ((iter-time (/ totaltime (car sum))) | ||
| 91 | (missing-time (- time totaltime)) | ||
| 92 | (missing-iter (/ missing-time iter-time))) | ||
| 93 | ;; `iter-time' is approximate because of effects like the GC, | ||
| 94 | ;; so multiply at most by 10, in case we are wildly off the mark. | ||
| 95 | (loop (max repetitions | ||
| 96 | (min (ceiling missing-iter) | ||
| 97 | (* 10 repetitions))) | ||
| 98 | sum)))))))) | ||
| 99 | |||
| 100 | ;;;###autoload | ||
| 44 | (defmacro benchmark-run (&optional repetitions &rest forms) | 101 | (defmacro benchmark-run (&optional repetitions &rest forms) |
| 45 | "Time execution of FORMS. | 102 | "Time execution of FORMS. |
| 46 | If REPETITIONS is supplied as a number, run FORMS that many times, | 103 | If REPETITIONS is supplied as a number, run FORMS that many times, |
| @@ -53,20 +110,7 @@ See also `benchmark-run-compiled'." | |||
| 53 | (unless (or (natnump repetitions) (and repetitions (symbolp repetitions))) | 110 | (unless (or (natnump repetitions) (and repetitions (symbolp repetitions))) |
| 54 | (setq forms (cons repetitions forms) | 111 | (setq forms (cons repetitions forms) |
| 55 | repetitions 1)) | 112 | repetitions 1)) |
| 56 | (let ((i (make-symbol "i")) | 113 | `(benchmark-call (lambda () ,@forms) ,repetitions)) |
| 57 | (gcs (make-symbol "gcs")) | ||
| 58 | (gc (make-symbol "gc"))) | ||
| 59 | `(let ((,gc gc-elapsed) | ||
| 60 | (,gcs gcs-done)) | ||
| 61 | (list ,(if (or (symbolp repetitions) (> repetitions 1)) | ||
| 62 | ;; Take account of the loop overhead. | ||
| 63 | `(- (benchmark-elapse (dotimes (,i ,repetitions) | ||
| 64 | ,@forms)) | ||
| 65 | (benchmark-elapse (dotimes (,i ,repetitions) | ||
| 66 | nil))) | ||
| 67 | `(benchmark-elapse ,@forms)) | ||
| 68 | (- gcs-done ,gcs) | ||
| 69 | (- gc-elapsed ,gc))))) | ||
| 70 | 114 | ||
| 71 | ;;;###autoload | 115 | ;;;###autoload |
| 72 | (defmacro benchmark-run-compiled (&optional repetitions &rest forms) | 116 | (defmacro benchmark-run-compiled (&optional repetitions &rest forms) |
| @@ -78,21 +122,7 @@ result. The overhead of the `lambda's is accounted for." | |||
| 78 | (unless (or (natnump repetitions) (and repetitions (symbolp repetitions))) | 122 | (unless (or (natnump repetitions) (and repetitions (symbolp repetitions))) |
| 79 | (setq forms (cons repetitions forms) | 123 | (setq forms (cons repetitions forms) |
| 80 | repetitions 1)) | 124 | repetitions 1)) |
| 81 | (let ((i (make-symbol "i")) | 125 | `(benchmark-call (byte-compile '(lambda () ,@forms)) ,repetitions)) |
| 82 | (gcs (make-symbol "gcs")) | ||
| 83 | (gc (make-symbol "gc")) | ||
| 84 | (code (byte-compile `(lambda () ,@forms))) | ||
| 85 | (lambda-code (byte-compile '(lambda ())))) | ||
| 86 | `(let ((,gc gc-elapsed) | ||
| 87 | (,gcs gcs-done)) | ||
| 88 | (list ,(if (or (symbolp repetitions) (> repetitions 1)) | ||
| 89 | ;; Take account of the loop overhead. | ||
| 90 | `(- (benchmark-elapse (dotimes (,i ,repetitions) | ||
| 91 | (funcall ,code))) | ||
| 92 | (benchmark-elapse (dotimes (,i ,repetitions) | ||
| 93 | (funcall ,lambda-code)))) | ||
| 94 | `(benchmark-elapse (funcall ,code))) | ||
| 95 | (- gcs-done ,gcs) (- gc-elapsed ,gc))))) | ||
| 96 | 126 | ||
| 97 | ;;;###autoload | 127 | ;;;###autoload |
| 98 | (defun benchmark (repetitions form) | 128 | (defun benchmark (repetitions form) |
| @@ -100,9 +130,15 @@ result. The overhead of the `lambda's is accounted for." | |||
| 100 | Interactively, REPETITIONS is taken from the prefix arg, and | 130 | Interactively, REPETITIONS is taken from the prefix arg, and |
| 101 | the command prompts for the form to benchmark. | 131 | the command prompts for the form to benchmark. |
| 102 | For non-interactive use see also `benchmark-run' and | 132 | For non-interactive use see also `benchmark-run' and |
| 103 | `benchmark-run-compiled'." | 133 | `benchmark-run-compiled'. |
| 134 | FORM can also be a function in which case we measure the time it takes | ||
| 135 | to call it without any argument." | ||
| 104 | (interactive "p\nxForm: ") | 136 | (interactive "p\nxForm: ") |
| 105 | (let ((result (eval `(benchmark-run ,repetitions ,form) t))) | 137 | (let ((result (benchmark-call (eval (pcase form |
| 138 | ((or `#',_ `(lambda . ,_)) form) | ||
| 139 | (_ `(lambda () ,form))) | ||
| 140 | t) | ||
| 141 | repetitions))) | ||
| 106 | (if (zerop (nth 1 result)) | 142 | (if (zerop (nth 1 result)) |
| 107 | (message "Elapsed time: %fs" (car result)) | 143 | (message "Elapsed time: %fs" (car result)) |
| 108 | (message "Elapsed time: %fs (%fs in %d GCs)" (car result) | 144 | (message "Elapsed time: %fs (%fs in %d GCs)" (car result) |