Skip to content

Commit bd48f0f

Browse files
committed
Add/Change: bench-dynamic-vs-lexical-binding and associated changes
1 parent 39e82ce commit bd48f0f

File tree

2 files changed

+1388
-1240
lines changed

2 files changed

+1388
-1240
lines changed

README.org

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -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

14381451
Used like:
@@ -1522,32 +1535,21 @@ So this macro showed which code is faster and helped catch a subtle bug.
15221535
To 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

16191621
The ~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

Comments
 (0)