aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStefan Monnier2021-03-17 19:04:28 -0400
committerStefan Monnier2021-03-17 19:04:28 -0400
commit82c3bd1e4a58f6fefcb7d69b6e04013bd86f54be (patch)
tree183c0845052e3d8a51c80e429b6333471a251b36
parent3cd3f230c924e5a095f37ea9c6ea6f9f6c5cf473 (diff)
downloademacs-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.texi3
-rw-r--r--etc/NEWS5
-rw-r--r--lisp/emacs-lisp/benchmark.el98
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
1043You can measure the time it takes to evaluate individual Emacs Lisp 1043You can measure the time it takes to evaluate individual Emacs Lisp
1044forms using the @file{benchmark} library. See the macros 1044forms 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.
diff --git a/etc/NEWS b/etc/NEWS
index 6fe98dbc123..27a4766a402 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -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.
394Additionally, the number of repetitions can be expressed as a minimal duration
395in 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.
48The result is a list (TIME GC GCTIME)
49where TIME is the total time it took, in seconds.
50GCTIME is the amount of time that was spent in the GC
51and GC is the number of times the GC was called.
52
53REPETITIONS can also be a floating point number, in which case it
54specifies a minimum number of seconds that the benchmark execution
55should take. In that case the return value is prepended with the
56number 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.
74Result 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.
46If REPETITIONS is supplied as a number, run FORMS that many times, 103If 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."
100Interactively, REPETITIONS is taken from the prefix arg, and 130Interactively, REPETITIONS is taken from the prefix arg, and
101the command prompts for the form to benchmark. 131the command prompts for the form to benchmark.
102For non-interactive use see also `benchmark-run' and 132For non-interactive use see also `benchmark-run' and
103`benchmark-run-compiled'." 133`benchmark-run-compiled'.
134FORM can also be a function in which case we measure the time it takes
135to 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)