ekp/tests/ekp-bench.el
Kinneyzhang fd1210e557 fix: close repository audit findings
Complete task002 through task016 across cache correctness, editor lifecycle, C boundaries, build and release governance, dictionary provenance, performance, interactive workflows, tests, documentation, and final cleanup.
2026-07-28 22:43:10 +08:00

101 lines
3.9 KiB
EmacsLisp

;;; ekp-bench.el --- Benchmarks for EKP -*- lexical-binding: t; -*-
;;; Commentary:
;; Performance benchmarks over the bundled sample texts. Run:
;;
;; # Pure Elisp engine
;; emacs -Q --batch -L . --eval '(setq ekp-use-c-module nil)' \
;; -l tests/ekp-bench.el
;;
;; # C module engine (build ekp_c first)
;; emacs -Q --batch -L . \
;; --eval '(progn (require (quote ekp)) (ekp-c-module-load))' \
;; -l tests/ekp-bench.el
;;
;; In batch mode widths are measured in character columns, so the
;; numbers are engine-comparable but not identical to GUI timings.
;;; Code:
(require 'ekp)
(require 'benchmark)
(defun ekp-bench--read (name)
(with-temp-buffer
(insert-file-contents
(expand-file-name name (expand-file-name "tests" (ekp-root-dir))))
(buffer-string)))
(defun ekp-bench-run (label thunk &optional n)
"Run THUNK N times (cold caches); report the fastest run."
(let ((n (or n 3)) (times nil))
(dotimes (_ n)
(ekp-clear-caches)
(garbage-collect)
(let ((t0 (float-time)))
(funcall thunk)
(push (- (float-time) t0) times)))
(message "%-42s %8.1f ms (min of %d)"
label (* 1000 (apply #'min times)) n)))
(defun ekp-bench-adversarial-builders (length)
"Measure tokenizer and dense insertion builders at LENGTH."
(let* ((word (make-string length ?a))
(positions (number-sequence 2 (- length 2) 8))
(split-time
(car (benchmark-run 3 (ekp-split-to-boxes word))))
(insert-time
(cl-letf (((symbol-function 'ekp-hyphen-positions)
(lambda (_h _word) positions)))
(car (benchmark-run 3
(ekp-hyphen-inserted nil word))))))
(message "builders n=%-5d split=%8.1f ms inserted=%8.1f ms breaks=%d"
length (* split-time 1000) (* insert-time 1000)
(length positions))))
(let* ((zh (ekp-bench--read "text-zh.txt"))
(en (ekp-bench--read "text-en_US.txt"))
(mix (ekp-bench--read "text-zh-en_US.txt"))
(zh3 (string-join (list zh zh zh) "\n")))
(message "== engine: %s ==" (if (and (boundp 'ekp-c-module-loaded)
ekp-c-module-loaded
ekp-use-c-module)
"C" "elisp"))
(ekp-bench-run "justify zh w=200" (lambda () (ekp-pixel-justify zh 200)))
(ekp-bench-run "justify zh w=400" (lambda () (ekp-pixel-justify zh 400)))
(ekp-bench-run "justify en w=200" (lambda () (ekp-pixel-justify en 200)))
(ekp-bench-run "justify en w=400" (lambda () (ekp-pixel-justify en 400)))
(ekp-bench-run "justify mix w=300" (lambda () (ekp-pixel-justify mix 300)))
(ekp-bench-run "justify zh3 w=400" (lambda () (ekp-pixel-justify zh3 400)))
(ekp-bench-run "range zh 340-380"
(lambda () (ekp-pixel-range-justify zh 340 380)))
(ekp-bench-run "range mix 280-320"
(lambda () (ekp-pixel-range-justify mix 280 320)))
(ekp-bench-run "para-create zh (all lines)"
(lambda () (dolist (s (split-string zh "\n"))
(unless (string-blank-p s) (ekp--get-para s)))))
;; DP only: paragraphs pre-tokenized, fresh DP each round
(let ((paras (cl-remove-if #'string-blank-p (split-string zh "\n")))
(times nil))
(dolist (s paras) (ekp--get-para s))
(dotimes (_ 3)
(dolist (s paras)
(clrhash (ekp-para-dp-cache (ekp--get-para s))))
(garbage-collect)
(let ((t0 (float-time)))
(dolist (s paras) (ekp-dp-cache s 400))
(push (- (float-time) t0) times)))
(message "%-42s %8.1f ms (min of 3)" "DP-only zh w=400 (paras cached)"
(* 1000 (apply #'min times)))))
(message "== adversarial builder scaling ==")
(dolist (length '(1000 2000 4000 8000))
(ekp-bench-adversarial-builders length))
(message "bench done")
(provide 'ekp-bench)
;;; ekp-bench.el ends here