diff options
| author | Stefan Monnier | 2026-01-09 22:34:32 -0500 |
|---|---|---|
| committer | Stefan Monnier | 2026-01-09 22:34:32 -0500 |
| commit | 058bac45b2f64dee35e26e22b00bafde41aa5aec (patch) | |
| tree | 92ed0175c6f37410a5cab2e8af7649fc968fe34a | |
| parent | 6c818936e00bf24201dbfa8916cd91aca24f84c9 (diff) | |
| download | emacs-058bac45b2f64dee35e26e22b00bafde41aa5aec.tar.gz emacs-058bac45b2f64dee35e26e22b00bafde41aa5aec.zip | |
cl-generic.el: Avoid an O(N^2) behavior
When N methods are defined, don't (re)build the dispatch
function each time since it takes O(N) time to build it.
* lisp/emacs-lisp/cl-generic.el (cl--generic-method): Add docstring.
(cl--generic): New `lazy-function` slot.
(cl--generic-make-function): Use it and delay building the dispatch
function until the next call.
[toplevel]: Simplify the bootstrap hacks a bit.
| -rw-r--r-- | lisp/emacs-lisp/cl-generic.el | 58 |
1 files changed, 37 insertions, 21 deletions
diff --git a/lisp/emacs-lisp/cl-generic.el b/lisp/emacs-lisp/cl-generic.el index ea73ce766f5..d501a421ea2 100644 --- a/lisp/emacs-lisp/cl-generic.el +++ b/lisp/emacs-lisp/cl-generic.el | |||
| @@ -154,17 +154,22 @@ also passed as second argument to SPECIALIZERS-FUNCTION." | |||
| 154 | (:constructor cl--generic-make-method | 154 | (:constructor cl--generic-make-method |
| 155 | (specializers qualifiers call-con function)) | 155 | (specializers qualifiers call-con function)) |
| 156 | (:predicate nil)) | 156 | (:predicate nil)) |
| 157 | "Type of `cl-generic' method objects. | ||
| 158 | FUNCTION holds a function containing the actual code of the method. | ||
| 159 | SPECIALIZERS holds the list of specializers (as long as the number of | ||
| 160 | mandatory arguments of the method). | ||
| 161 | QUALIFIERS holds the list of qualifiers. | ||
| 162 | CALL-CON indicates the calling convention expected by FUNCTION: | ||
| 163 | - nil: FUNCTION is just a normal function with no extra arguments for | ||
| 164 | `call-next-method' or `next-method-p' (which it hence can't use). | ||
| 165 | - `curried': FUNCTION is a curried function that first takes the | ||
| 166 | \"next combined method\" and returns the resulting combined method. | ||
| 167 | It can distinguish `next-method-p' by checking if that next method | ||
| 168 | is `cl--generic-isnot-nnm-p'. | ||
| 169 | - t: FUNCTION takes the `call-next-method' function as an extra first | ||
| 170 | argument." | ||
| 157 | (specializers nil :read-only t :type list) | 171 | (specializers nil :read-only t :type list) |
| 158 | (qualifiers nil :read-only t :type (list-of atom)) | 172 | (qualifiers nil :read-only t :type (list-of atom)) |
| 159 | ;; CALL-CON indicates the calling convention expected by FUNCTION: | ||
| 160 | ;; - nil: FUNCTION is just a normal function with no extra arguments for | ||
| 161 | ;; `call-next-method' or `next-method-p' (which it hence can't use). | ||
| 162 | ;; - `curried': FUNCTION is a curried function that first takes the | ||
| 163 | ;; "next combined method" and return the resulting combined method. | ||
| 164 | ;; It can distinguish `next-method-p' by checking if that next method | ||
| 165 | ;; is `cl--generic-isnot-nnm-p'. | ||
| 166 | ;; - t: FUNCTION takes the `call-next-method' function as its first (extra) | ||
| 167 | ;; argument. | ||
| 168 | (call-con nil :read-only t :type symbol) | 173 | (call-con nil :read-only t :type symbol) |
| 169 | (function nil :read-only t :type function)) | 174 | (function nil :read-only t :type function)) |
| 170 | 175 | ||
| @@ -181,7 +186,10 @@ also passed as second argument to SPECIALIZERS-FUNCTION." | |||
| 181 | ;; The most important dispatch is last in the list (and the least is first). | 186 | ;; The most important dispatch is last in the list (and the least is first). |
| 182 | (dispatches nil :type (list-of (cons natnum (list-of generalizers)))) | 187 | (dispatches nil :type (list-of (cons natnum (list-of generalizers)))) |
| 183 | (method-table nil :type (list-of cl--generic-method)) | 188 | (method-table nil :type (list-of cl--generic-method)) |
| 184 | (options nil :type list)) | 189 | (options nil :type list) |
| 190 | ;; This slot holds the function we put into `symbol-function' before | ||
| 191 | ;; the actual dispatch function has been computed. | ||
| 192 | (lazy-function nil)) | ||
| 185 | 193 | ||
| 186 | (defun cl-generic-function-options (generic) | 194 | (defun cl-generic-function-options (generic) |
| 187 | "Return the options of the generic function GENERIC." | 195 | "Return the options of the generic function GENERIC." |
| @@ -658,8 +666,6 @@ The set of acceptable TYPEs (also called \"specializers\") is defined | |||
| 658 | ;; Keep the ordering; important for methods with :extra qualifiers. | 666 | ;; Keep the ordering; important for methods with :extra qualifiers. |
| 659 | (mapcar (lambda (x) (if (eq x (car me)) method x)) mt))) | 667 | (mapcar (lambda (x) (if (eq x (car me)) method x)) mt))) |
| 660 | (let ((sym (cl--generic-name generic)) ; Actual name (for aliases). | 668 | (let ((sym (cl--generic-name generic)) ; Actual name (for aliases). |
| 661 | ;; FIXME: Try to avoid re-constructing a new function if the old one | ||
| 662 | ;; is still valid (e.g. still empty method cache)? | ||
| 663 | (gfun (cl--generic-make-function generic))) | 669 | (gfun (cl--generic-make-function generic))) |
| 664 | (cl-pushnew `(cl-defmethod . ,(cl--generic-load-hist-format | 670 | (cl-pushnew `(cl-defmethod . ,(cl--generic-load-hist-format |
| 665 | (cl--generic-name generic) | 671 | (cl--generic-name generic) |
| @@ -827,9 +833,24 @@ You might need to add: %S" | |||
| 827 | ,@fixedargs args))))))))) | 833 | ,@fixedargs args))))))))) |
| 828 | 834 | ||
| 829 | (defun cl--generic-make-function (generic) | 835 | (defun cl--generic-make-function (generic) |
| 830 | (cl--generic-make-next-function generic | 836 | "Return the function to put into the `symbol-function' of GENERIC." |
| 831 | (cl--generic-dispatches generic) | 837 | ;; The function we want is the one that performs the dispatch, |
| 832 | (cl--generic-method-table generic))) | 838 | ;; but that function depends on the set of methods and needs to be |
| 839 | ;; flushed/recomputed when the set of methods changes. | ||
| 840 | ;; To avoid reconstructing such a method N times for N `cl-defmethod', | ||
| 841 | ;; we construct the dispatch function lazily: | ||
| 842 | ;; we first return a "lazy" function, which waits until the | ||
| 843 | ;; first call to the method to really compute the dispatch function, | ||
| 844 | ;; at which point we replace the dummy with the real one. | ||
| 845 | (with-memoization (cl--generic-lazy-function generic) | ||
| 846 | (lambda (&rest args) | ||
| 847 | (let ((real | ||
| 848 | (cl--generic-make-next-function generic | ||
| 849 | (cl--generic-dispatches generic) | ||
| 850 | (cl--generic-method-table generic)))) | ||
| 851 | (let ((current-load-list nil)) | ||
| 852 | (defalias (cl--generic-name generic) real)) | ||
| 853 | (apply real args))))) | ||
| 833 | 854 | ||
| 834 | (defun cl--generic-make-next-function (generic dispatches methods) | 855 | (defun cl--generic-make-next-function (generic dispatches methods) |
| 835 | (let* ((dispatch | 856 | (let* ((dispatch |
| @@ -985,10 +1006,6 @@ FUN is the function that should be called when METHOD calls | |||
| 985 | (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car)))) | 1006 | (setq methods (nreverse (mapcar #'cdr (sort methods #'car-less-than-car)))) |
| 986 | (cl--generic-make-next-function generic dispatches-left methods))) | 1007 | (cl--generic-make-next-function generic dispatches-left methods))) |
| 987 | 1008 | ||
| 988 | (unless (ignore-errors (cl-generic-generalizers t)) | ||
| 989 | ;; Temporary definition to let the next defgenerics succeed. | ||
| 990 | (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination)) | ||
| 991 | |||
| 992 | (cl-defgeneric cl-generic-generalizers (specializer) | 1009 | (cl-defgeneric cl-generic-generalizers (specializer) |
| 993 | "Return a list of generalizers for a given SPECIALIZER. | 1010 | "Return a list of generalizers for a given SPECIALIZER. |
| 994 | To each kind of `specializer', corresponds a `generalizer' which describes | 1011 | To each kind of `specializer', corresponds a `generalizer' which describes |
| @@ -1031,8 +1048,7 @@ those methods.") | |||
| 1031 | (unless (ignore-errors (cl-generic-generalizers t)) | 1048 | (unless (ignore-errors (cl-generic-generalizers t)) |
| 1032 | ;; Temporary definition to let the next defmethod succeed. | 1049 | ;; Temporary definition to let the next defmethod succeed. |
| 1033 | (fset 'cl-generic-generalizers | 1050 | (fset 'cl-generic-generalizers |
| 1034 | (lambda (specializer) | 1051 | (lambda (_specializer) (list cl--generic-t-generalizer))) |
| 1035 | (if (eq t specializer) (list cl--generic-t-generalizer)))) | ||
| 1036 | (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination)) | 1052 | (fset 'cl-generic-combine-methods #'cl--generic-standard-method-combination)) |
| 1037 | 1053 | ||
| 1038 | (cl-defmethod cl-generic-generalizers (specializer) | 1054 | (cl-defmethod cl-generic-generalizers (specializer) |