Optimizing Lookup Functions Using LOAD-TIME-VALUE #
hacking, March 1st 2011
Consider code along the lines of this:
(defvar *foo-table* (make-hash-table))
(defun find-foo (name &optional errorp)
(or (gethash name *foo-table*)
(when errorp
(error "No FOO called ~S." name))))
;;; Style Cop says: It is good form to keep the interfaces identical,
;;; even though the SETF version doesn't use the ERRORP.
(defun (setf find-foo) (foo name &optional errorp)
(declare (ignore errorp))
(setf (gethash name *foo-table*) foo))
Assuming that cases with constant NAME arguments exist, how to optimize them — aside from custom hashing schemes, etc?
- Make *FOO-TABLE* hold cells holding the actual objects.
- Use LOAD-TIME-VALUE to grab hold of the cell inline.
- (SETF FIND-FOO) will first grab the cell and then update it.
Thusly:
(defvar *foo-table* (make-hash-table))
(defun find-foo-cell (name create)
(or (gethash name *foo-table*)
(when create
(setf (gethash name *foo-table*)
(cons name nil)))))
(defun foo-from-cell (cell errorp &optional name)
(or (cdr cell)
(when errorp
(error "No FOO called ~S." (or (car cell) name)))))
(defun find-foo (name &optional errorp)
(foo-from-cell (find-foo-cell name nil) errorp name))
(define-compiler-macro find-foo (&whole form name &optional errorp)
(if (constantp name)
`(foo-from-cell (load-time-value (find-foo-cell ,name t)) ,errorp)
form))
(defun (setf find-foo) (foo name &optional errorp)
(declare (ignore errorp))
(setf (cdr (find-foo-cell name t)) foo))
(define-compiler-macro (setf find-foo)
(&whole form value name &optional errorp)
(declare (ignore errorp))
(if (constantp name)
`(setf (cdr (load-time-value (find-foo-cell ,name t))) ,value)
form))
...and then there are no hash-table accesses at runtime for the constant argument cases.
Depending on your implementation's support for SETF-compiler-macros, you may need to replace the SETF-function with>
(defsetf find-foo set-foo) ; then SET-FOO and a compiler-macro for it
... but the same principle holds.