@@ -1346,32 +1346,38 @@ When called from an Org source block, it gives output like this:
13461346|---------------+----------+--------------------|
13471347| 1.657838266 | 3 | 1.4723854609999876 |
13481348
1349- **** ~bench-multi~
1349+ **** ~bench-multi~ macros
13501350
1351- This macro makes comparing multiple forms easy:
1351+ These macros make comparing multiple forms easy:
13521352
13531353#+BEGIN_SRC elisp :exports code :results silent
1354- (cl-defmacro bench-multi (&key (times 1) forms ensure-equal)
1354+ (cl-defmacro bench-multi (&key (times 1) forms ensure-equal raw )
13551355 "Return Org table as a list with benchmark results for FORMS.
1356- Runs FORMS with `benchmark-run-compiled' for TIMES iterations.
1356+ Runs FORMS with `benchmark-run-compiled' for TIMES iterations.
13571357
1358- When ENSURE-EQUAL is non-nil, the results of FORMS are compared,
1359- and an error is raised if they aren't `equal'. If the results
1360- are sequences, the difference between them is shown with
1361- `seq-difference'.
1358+ When ENSURE-EQUAL is non-nil, the results of FORMS are compared,
1359+ and an error is raised if they aren't `equal'. If the results
1360+ are sequences, the difference between them is shown with
1361+ `seq-difference'.
13621362
1363- If the first element of a form is a string, it's used as the
1364- form's description in the bench-multi-results; otherwise, forms
1365- are numbered from 0 .
1363+ When RAW is non-nil, the raw results from
1364+ `benchmark-run-compiled' are returned instead of an Org table
1365+ list .
13661366
1367- Before each form is run, `garbage-collect' is called."
1367+ If the first element of a form is a string, it's used as the
1368+ form's description in the bench-multi-results; otherwise, forms
1369+ are numbered from 0.
1370+
1371+ Before each form is run, `garbage-collect' is called."
13681372 ;; MAYBE: Since `bench-multi-lexical' byte-compiles the file, I'm not sure if
13691373 ;; `benchmark-run-compiled' is necessary over `benchmark-run', or if it matters.
13701374 (declare (indent defun))
1371- (let ((keys (gensym "keys"))
1375+ (let* ((keys (gensym "keys"))
13721376 (result-times (gensym "result-times"))
13731377 (header '(("Form" "x faster than next" "Total runtime" "# of GCs" "Total GC runtime")
13741378 hline))
1379+ ;; Copy forms so that a subsequent call of the macro will get the original forms.
1380+ (forms (copy-list forms))
13751381 (descriptions (cl-loop for form in forms
13761382 for i from 0
13771383 collect (if (stringp (car form))
@@ -1418,21 +1424,28 @@ This macro makes comparing multiple forms easy:
14181424 (gethash (nth i ,keys) bench-multi-results)
14191425 (gethash (nth (1+ i) ,keys) bench-multi-results)))))
14201426 ;; Add factors to times and return table
1421- (append ',header
1422- (cl-loop with length = (length ,result-times)
1423- for i from 0 to (1- length)
1424- for description = (car (nth i ,result-times))
1425- for factor = (if (< i (1- length))
1426- (format "%.2f" (/ (second (nth (1+ i) ,result-times))
1427- (second (nth i ,result-times))))
1428- "slowest")
1429- collect (append (list description factor)
1430- (list (format "%.6f" (second (nth i ,result-times)))
1431- (third (nth i ,result-times))
1432- (if (> (fourth (nth i ,result-times)) 0)
1433- (format "%.6f" (fourth (nth i ,result-times)))
1434- 0)))))))
1427+ (if ,raw
1428+ ,result-times
1429+ (append ',header
1430+ (bench-multi-process-results ,result-times)))))
14351431 (unintern 'bench-multi-results nil))))
1432+
1433+ (defun bench-multi-process-results (results)
1434+ "Return sorted RESULTS with factors added."
1435+ (setq results (sort results (-on #'< #'second)))
1436+ (cl-loop with length = (length results)
1437+ for i from 0 to (1- length)
1438+ for description = (car (nth i results))
1439+ for factor = (if (< i (1- length))
1440+ (format "%.2f" (/ (second (nth (1+ i) results))
1441+ (second (nth i results))))
1442+ "slowest")
1443+ collect (append (list description factor)
1444+ (list (format "%.6f" (second (nth i results)))
1445+ (third (nth i results))
1446+ (if (> (fourth (nth i results)) 0)
1447+ (format "%.6f" (fourth (nth i results)))
1448+ 0)))))
14361449#+END_SRC
14371450
14381451Used like:
@@ -1522,32 +1535,21 @@ So this macro showed which code is faster and helped catch a subtle bug.
15221535To evaluate forms with lexical binding enabled, use this macro:
15231536
15241537#+BEGIN_SRC elisp :exports code :results silent
1525- (cl-defmacro bench-multi-lexical (&key (times 1) forms ensure-equal)
1538+ (cl-defmacro bench-multi-lexical (&key (times 1) forms ensure-equal raw )
15261539 "Return Org table as a list with benchmark results for FORMS.
15271540 Runs FORMS from a byte-compiled temp file with `lexical-binding'
15281541 enabled, using `bench-multi', which see.
15291542
1530- When ENSURE-EQUAL is non-nil, the results of FORMS are compared,
1531- and an error is raised if they aren't `equal'. If the results
1532- are sequences, the difference between them is shown with
1533- `seq-difference'.
1534-
1535- If the first element of a form is a string, it's used as the
1536- form's description in the bench-multi-results; otherwise, forms
1537- are numbered from 0.
1538-
1539- Before each form is run, `garbage-collect' is called.
1540-
15411543 Afterward, the temp file is deleted and the function used to run
1542- the benchmark is uninterned.. "
1544+ the benchmark is uninterned."
15431545 (declare (indent defun))
15441546 `(let* ((temp-file (concat (make-temp-file "bench-multi-lexical-") ".el"))
15451547 (fn (gensym "bench-multi-lexical-run-")))
15461548 (with-temp-file temp-file
15471549 (insert ";; -*- lexical-binding: t; -*-" "\n\n"
15481550 "(defvar bench-multi-results)" "\n\n"
1549- (format "(defun %s () (bench-multi :times %d :ensure-equal %s :forms %S))"
1550- fn ,times ,ensure-equal ',forms)))
1551+ (format "(defun %s () (bench-multi :times %d :ensure-equal %s :raw %s : forms %S))"
1552+ fn ,times ,ensure-equal ,raw ',forms)))
15511553 (unwind-protect
15521554 (if (byte-compile-file temp-file 'load)
15531555 (funcall (intern (symbol-name fn)))
@@ -1618,6 +1620,48 @@ This shows that lexical-binding doesn't make much difference in this example. B
16181620
16191621The ~buffer-local-value~ form improved by about 24% when using lexical binding.
16201622
1623+ ***** ~bench-dynamic-vs-lexical-binding~
1624+
1625+ This macro compares dynamic and lexical binding.
1626+
1627+ #+BEGIN_SRC elisp :exports code :results silent
1628+ (cl-defmacro bench-dynamic-vs-lexical-binding (&key (times 1) forms ensure-equal)
1629+ "Benchmark FORMS with both dynamic and lexical binding.
1630+ Calls `bench-multi' and `bench-multi-lexical', which see."
1631+ (declare (indent defun))
1632+ `(let ((dynamic (bench-multi :times ,times :ensure-equal ,ensure-equal :raw t
1633+ :forms ,forms))
1634+ (lexical (bench-multi-lexical :times ,times :ensure-equal ,ensure-equal :raw t
1635+ :forms ,forms))
1636+ (header '("Form" "x faster than next" "Total runtime" "# of GCs" "Total GC runtime")))
1637+ (cl-loop for result in-ref dynamic
1638+ do (setf (car result) (format "Dynamic: %s" (car result))))
1639+ (cl-loop for result in-ref lexical
1640+ do (setf (car result) (format "Lexical: %s" (car result))))
1641+ (append (list header)
1642+ (list 'hline)
1643+ (bench-multi-process-results (append dynamic lexical)))))
1644+ #+END_SRC
1645+
1646+ Example:
1647+
1648+ #+BEGIN_SRC elisp :exports both :cache yes
1649+ (bench-dynamic-vs-lexical-binding :times 1000 :ensure-equal t
1650+ :forms (("buffer-local-value" (--filter (equal 'magit-status-mode (buffer-local-value 'major-mode it))
1651+ (buffer-list)))
1652+ ("with-current-buffer" (--filter (equal 'magit-status-mode (with-current-buffer it
1653+ major-mode))
1654+ (buffer-list)))))
1655+ #+END_SRC
1656+
1657+ #+RESULTS[73cc92a5949dd2d48f029cab9557eb6132bdf1cf]:
1658+ | Form | x faster than next | Total runtime | # of GCs | Total GC runtime |
1659+ |------------------------------+--------------------+---------------+----------+------------------|
1660+ | Lexical: buffer-local-value | 1.29 | 0.011170 | 0 | 0 |
1661+ | Dynamic: buffer-local-value | 67.10 | 0.014407 | 0 | 0 |
1662+ | Lexical: with-current-buffer | 1.01 | 0.966651 | 0 | 0 |
1663+ | Dynamic: with-current-buffer | slowest | 0.974830 | 0 | 0 |
1664+
16211665**** =elp-profile=
16221666:PROPERTIES:
16231667:ID: fd3fdece-0342-441c-8540-5a5c463890a5
0 commit comments