Fix consult, tempel keybind, add benchmark macros, some rust templates
179 lines
9.1 KiB
EmacsLisp
179 lines
9.1 KiB
EmacsLisp
;;; 99-external-misc.el --- Miscellaneous functions made by others -*- lexical-binding: t -*-
|
|
|
|
;; This file is not part of GNU Emacs
|
|
|
|
;;; Commentary:
|
|
|
|
;; This file contains various useful functions and macros that I definitely
|
|
;; didn't write and took from somewhere as-is.
|
|
|
|
;;; Code:
|
|
|
|
(defun second (item)
|
|
(nth 1 item))
|
|
|
|
(defun third (item)
|
|
(nth 2 item))
|
|
|
|
(defun fourth (item)
|
|
(nth 3 item))
|
|
|
|
;; Source: https://phillord.github.io/m-buffer-el/#sec-5-1-2
|
|
;;;###autoload
|
|
(cl-defmacro bench (&optional (times 100000) &rest body)
|
|
"Call `benchmark-run-compiled' on BODY with TIMES iterations, returning list suitable for Org source block evaluation.
|
|
Garbage is collected before calling `benchmark-run-compiled' to
|
|
avoid counting existing garbage which needs collection."
|
|
(declare (indent defun))
|
|
`(progn
|
|
(garbage-collect)
|
|
(list '("Total runtime" "# of GCs" "Total GC runtime")
|
|
'hline
|
|
(benchmark-run-compiled ,times
|
|
(progn
|
|
,@body)))))
|
|
|
|
;; Source: alphapapa.github.io/emacs-package-dev-handbook/#outline-container-bench-multi-macros
|
|
;;;###autoload
|
|
(cl-defmacro bench-multi (&key (times 1) forms ensure-equal raw)
|
|
"Return Org table as a list with benchmark results for FORMS.
|
|
Runs FORMS with `benchmark-run-compiled' for TIMES iterations.
|
|
|
|
When ENSURE-EQUAL is non-nil, the results of FORMS are compared,
|
|
and an error is raised if they aren't `equal'. If the results are
|
|
sequences, the difference between them is shown with
|
|
`seq-difference'.
|
|
|
|
When RAW is non-nil, the raw results from
|
|
`benchmark-run-compiled' are returned instead of an Org table
|
|
list.
|
|
|
|
If the first element of a form is a string, it's used as the
|
|
form's description in the bench-multi-results; otherwise, forms
|
|
are numbered from 0.
|
|
|
|
Before each form is run, `garbage-collect' is called.
|
|
|
|
See source for a usage example."
|
|
;; MAYBE: Since `bench-multi-lexical' byte-compiles the file, I'm not sure if
|
|
;; `benchmark-run-compiled' is necessary over `benchmark-run', or if it matters.
|
|
(declare (indent defun))
|
|
(let*((keys (gensym "keys"))
|
|
(result-times (gensym "result-times"))
|
|
(header '(("Form" "x fastest" "Total runtime" "# of GCs" "Total GC runtime")
|
|
hline))
|
|
;; Copy forms so that a subsequent call of the macro will get the original forms.
|
|
(forms (cl-copy-list forms))
|
|
(descriptions (cl-loop for form in forms
|
|
for i from 0
|
|
collect (if (stringp (car form))
|
|
(prog1 (car form)
|
|
(setf (nth i forms) (cadr (nth i forms))))
|
|
i))))
|
|
`(unwind-protect
|
|
(progn
|
|
(defvar bench-multi-results nil)
|
|
(let* ((bench-multi-results (make-hash-table))
|
|
(,result-times (sort (list ,@(cl-loop for form in forms
|
|
for i from 0
|
|
for description = (nth i descriptions)
|
|
collect `(progn
|
|
(garbage-collect)
|
|
(cons ,description
|
|
(benchmark-run-compiled ,times
|
|
,(if ensure-equal
|
|
`(puthash ,description ,form bench-multi-results)
|
|
form))))))
|
|
(lambda (a b)
|
|
(< (nth 1 a) (nth 1 b))))))
|
|
,(when ensure-equal
|
|
`(cl-loop with ,keys = (hash-table-keys bench-multi-results)
|
|
for i from 0 to (- (length ,keys) 2)
|
|
unless (equal (gethash (nth i ,keys) bench-multi-results)
|
|
(gethash (nth (1+ i) ,keys) bench-multi-results))
|
|
do (if (sequencep (gethash (car (hash-table-keys bench-multi-results)) bench-multi-results))
|
|
(let* ((k1) (k2)
|
|
;; If the difference in one order is nil, try in other order.
|
|
(difference (or (setq k1 (nth i ,keys)
|
|
k2 (nth (1+ i) ,keys)
|
|
difference (seq-difference (gethash k1 bench-multi-results)
|
|
(gethash k2 bench-multi-results)))
|
|
(setq k1 (nth (1+ i) ,keys)
|
|
k2 (nth i ,keys)
|
|
difference (seq-difference (gethash k1 bench-multi-results)
|
|
(gethash k2 bench-multi-results))))))
|
|
(user-error "Forms' bench-multi-results not equal: difference (%s - %s): %S"
|
|
k1 k2 difference))
|
|
;; Not a sequence
|
|
(user-error "Forms' bench-multi-results not equal: %s:%S %s:%S"
|
|
(nth i ,keys) (nth (1+ i) ,keys)
|
|
(gethash (nth i ,keys) bench-multi-results)
|
|
(gethash (nth (1+ i) ,keys) bench-multi-results)))))
|
|
;; Add factors to times and return table
|
|
(if ,raw
|
|
,result-times
|
|
(append ',header
|
|
(bench-multi-process-results ,result-times)))))
|
|
(unintern 'bench-multi-results nil))))
|
|
|
|
;; Use like this:
|
|
;; (bench-multi
|
|
;; :forms (("org-map-entries" (sort (org-map-entries (lambda ()
|
|
;; (nth 4 (org-heading-components)))
|
|
;; "/+MAYBE" 'agenda)
|
|
;; #'string<))
|
|
;; ("regexp" (sort (-flatten
|
|
;; (-non-nil
|
|
;; (mapcar (lambda (file)
|
|
;; (let ((case-fold-search t))
|
|
;; (with-current-buffer (find-buffer-visiting file)
|
|
;; (org-with-wide-buffer
|
|
;; (goto-char (point-min))
|
|
;; (cl-loop with regexp = (format org-heading-keyword-regexp-format "MAYBE")
|
|
;; while (re-search-forward regexp nil t)
|
|
;; collect (nth 4 (org-heading-components)))))))
|
|
;; (org-agenda-files))))
|
|
;; #'string<))))
|
|
|
|
(defun bench-multi-process-results (results)
|
|
"Return sorted RESULTS with factors added."
|
|
(setq results (sort results (-on #'< #'second)))
|
|
(cl-loop with length = (length results)
|
|
for i from 0 below length
|
|
for description = (car (nth i results))
|
|
for factor = (pcase i
|
|
(0 "fastest")
|
|
(_ (format "%.2f" (/ (second (nth i results))
|
|
(second (nth 0 results))))))
|
|
collect (append (list description factor)
|
|
(list (format "%.6f" (second (nth i results)))
|
|
(third (nth i results))
|
|
(if (> (fourth (nth i results)) 0)
|
|
(format "%.6f" (fourth (nth i results)))
|
|
0)))))
|
|
|
|
;;;###autoload
|
|
(cl-defmacro bench-multi-lexical (&key (times 1) forms ensure-equal raw)
|
|
"Return Org table as a list with benchmark results for FORMS.
|
|
Runs FORMS from a byte-compiled temp file with `lexical-binding'
|
|
enabled, using `bench-multi', which see.
|
|
|
|
Afterward, the temp file is deleted and the function used to run
|
|
the benchmark is uninterned."
|
|
(declare (indent defun))
|
|
`(let* ((temp-file (concat (make-temp-file "bench-multi-lexical-") ".el"))
|
|
(fn (gensym "bench-multi-lexical-run-")))
|
|
(with-temp-file temp-file
|
|
(insert ";; -*- lexical-binding: t; -*-" "\n\n"
|
|
"(defvar bench-multi-results)" "\n\n"
|
|
(format "(defun %s () (bench-multi :times %d :ensure-equal %s :raw %s :forms %S))"
|
|
fn ,times ,ensure-equal ,raw ',forms)))
|
|
(unwind-protect
|
|
(if (byte-compile-file temp-file 'load)
|
|
(funcall (intern (symbol-name fn)))
|
|
(user-error "Error byte-compiling and loading temp file"))
|
|
(delete-file temp-file)
|
|
(unintern (symbol-name fn) nil))))
|
|
|
|
;;; 99-external-misc.el ends here
|