Elisp: Benchmark, Test Speed

By Xah Lee. Date: . Last updated: .

Benchmark Library

a benchmark lib is bundled with emacs.

(require 'benchmark)

Benchmark Function

benchmark-run
(benchmark-run &optional REPETITIONS &rest FORMS)

Time execution of FORMS.

If REPETITIONS is supplied as a number, run FORMS that many times, accounting for the overhead of the resulting loop. Otherwise run FORMS once.

Return a list of the total elapsed time for execution, the number of garbage collections that ran, and the time taken by garbage collection. See also benchmark-run-compiled .

Example: Byte Compiled Code

;; -*- coding: utf-8; lexical-binding: t; -*-
;; 2023-08-07
;; comparing speed of compiled code

(require 'benchmark)

(defun xtest (n)
  "Return the time, in seconds, to run N iterations of a loop."
  (while (> (setq n (1- n)) 0))
  n
  )

(setq xi 10000000)

(benchmark-run 1 (xtest xi))
;; (0.7 0 0.0)

;; using benchmark-run-compiled does not make a difference. strange
(benchmark-run-compiled 1 (xtest xi))
;; (0.6 0 0.0)

;; actually compile the code make a difference
(byte-compile 'xtest)
(benchmark-run 1 (xtest xi))
;; (0.1 0 0.0)

;; bytecompiled code is some 7 times faster in this example

Example: current-word vs get-thing-at-point

;; -*- coding: utf-8; lexical-binding: t; -*-
;; 2023-08-02 2023-08-07
;; comparing speed of ways to get current word

(require 'benchmark)

(setq xi 100000)

(benchmark-run xi (current-word))
;; some
;; (0.19 7 0.15)

(benchmark-run xi (xah-get-thing-at-point 'word))
;; some
;; (0.55 14 0.33)

;; byte compiled version
(benchmark-run xi (xah-get-thing-at-point 'word))
;; some
;; (0.24 5 0.11)

(benchmark-run xi (thing-at-point 'word))
;; some
;; (1.1 28 0.6)

Example: diff ways of checking the char before cursor is any of whitespace

;; -*- coding: utf-8; lexical-binding: t; -*-
;; 2023-08-02
;; comparing speed of diff ways of checking the char before cursor is any of whitespace

(require 'benchmark)

(setq xi 1000000)

(benchmark-run xi
  (progn
    (or
     (eq (char-before) 32)
     (eq (char-before) 9)
     (eq (char-before) 10))))
;; (0.21 0 0.0)

(benchmark-run xi
  (if (eq (point-min) (point))
      nil
    (prog2
        (backward-char)
        (looking-at "[ \t\n]")
      (forward-char))))
;; (0.43 0 0.0)

(benchmark-run xi
  (string-match "[ \t\n]" (char-to-string (char-before))))
;; (0.73 20 0.47)

(benchmark-run xi (looking-back "[ \t\n]"))
;; (1.5 43 1.0)

(benchmark-run xi (looking-back "[ \t\n]" (1- (point))))
;; (1.6 42 1.0)

Example: Remove Prefx String

;; speed of replace a prefix string

(setq
 xstr "00000000000000000000000000000000000000000000000ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt0000000000000000000000"
 xpre "00000000000000000000000000000000000000000000000"
 xcount 10000
 )

;; using builtin
(benchmark-run xcount
  (require 'subr-x)
  (string-remove-prefix xpre xstr))
;; (0.02 1 0.02)

;; manual string manipulation
(benchmark-run xcount
  (let (xhead)
    (setq xhead (substring xstr 0 (length xpre)))
    (if (string-equal xpre xhead)
        (progn (substring xstr (length xhead)))
      xstr)))
;; (0.03 1 0.02)

;; use replace-regexp-in-string
(benchmark-run xcount
  (let (xhead)
    (replace-regexp-in-string (concat "^" xpre) "" xstr t t)))
;; (0.07 2 0.04)

;; use a temp buffer
(benchmark-run xcount
  (let (xhead)
    (with-temp-buffer
      (insert xstr)
      (goto-char (point-min))
      (when (search-forward xpre nil t)
        (delete-region (point-min) (point)))
      (buffer-substring (point-min) (point-max)))))
;; (0.24 6 0.13)

Example: Test a Script

(benchmark-run 1 (xahsite-update-search))
;; (15.8 34 0.71)

Example: insert per list item vs join list and insert once

;; 2024-01-24
;; compare the speed of inserting huge list into a buffer
;; insert one item at a time
;; vs
;; mapconcat and insert just once

;; conclusion: insert just once is a magnitude faster.
;; the more items, the more slow is insert per item

(setq xpaths nil)
(dotimes (i 2000 xpaths) (push (number-to-string i) xpaths))

(benchmark-run 10
  (let ((xbuf (get-buffer-create "xx888" t)))
    (with-current-buffer xbuf
      (erase-buffer)
      (mapc (lambda (x) (insert x "\n")) xpaths))
    (display-buffer xbuf)))
;; (0.1 0 0.0)

(benchmark-run 10
  (let ((xbuf (get-buffer-create "xx888" t)))
    (with-current-buffer xbuf
      (erase-buffer)
      (insert (mapconcat 'identity xpaths "\n")))
    (display-buffer xbuf)))
;; (0.001 0 0.0)

Example: Speed of Set Values in Vector to Variable

;; -*- coding: utf-8; lexical-binding: t; -*-
;; 2024-03-19
;; comparing speed of ways to set vector of length 2 to 2 vars
;; the values is is supposed from a function that return [begin end] positions of html tag.

;; xBeginEndVec is a result of a function, eg get-tag-boundary
(setq xBeginEndVec (vector 300 400))

;; traditional way, use a temp var
(defun xx1 ()
  (let (xbeg xend)
    (let ((xtmp xBeginEndVec))
      (setq xbeg (aref xtmp 0) xend (aref xtmp 1)))))

;; using lambda to hold data. eval once.
(defun xx2 ()
  (let (xbeg xend)
    ((lambda (x) (setq xbeg (aref x 0) xend (aref x 1))) xBeginEndVec)))

;; using pattern matching
(defun xx3 ()
  (let (xbeg xend)
    (seq-setq (xbeg xend) xBeginEndVec)))

(require 'benchmark)

(setq xi 100000)

(benchmark-run xi (xx1))
;; (0.14 5 0.09)

(benchmark-run xi (xx2))
;; (0.14 5 0.09)

(benchmark-run xi (xx3))
;; (0.19 6 0.12)

Example: nreverse vs reverse

;; -*- coding: utf-8; lexical-binding: t; -*-
;; 2024-03-22
;; comparing speed of nreverse vs reverse
;; nreverse is destructive, reverse is not
;; so, nreverse should be faster? else why two?

;; conclusion. both are too fast to tell

;; turns out, both are written in c

(require 'benchmark)

(setq xlist (number-sequence 1 10000))

(benchmark-run 1 (setq x1 (nreverse xlist)))
;; (6.8e-05 0 0.0)

(benchmark-run 1 (setq x2 (reverse xlist)))
;; (2e-06 0 0.0)

Example: sort vs seq-sort

;; -*- coding: utf-8; lexical-binding: t; -*-
;; 2024-03-22
;; comparing speed of sort vs seq-sort
;; sort is destructive vs seq-sort is not

;; sort is written in c

;; conclusion, sort is 10 times faster, but only if have a list of ten thousands items to be able to tell

(require 'benchmark)

(setq xlist1
      (mapcar
       (lambda (x) (random 999))
       (number-sequence 1 10000)))

(setq xlist2 (copy-sequence xlist1))

(equal xlist1 xlist2)
;; t

(benchmark-run 1 (setq xresult1 (sort xlist1 '> )) )
;; (0.0003 0 0.0)

(benchmark-run 1 (setq xresult2 (seq-sort '> xlist2 )))
;; (0.003 0 0.0)

(equal xresult1 xresult2)
;; t

Elisp, scripts