您实际上可能会访问哈希表 3 次。为什么?因为push 宏可能会扩展为执行gethash 获取列表的代码,然后执行一些system::sethash 操作来存储值。
在这个问题中,您正在检查一个位置的值,它是一个列表。如果该列表满足某些谓词测试,那么您将某些内容推送到该位置。
可以通过创建捕获此语义的专用运算符来解决此问题:
(push-if <new-value> <predicate> <place>)
例如:
(push-if i #'may-add (gethash complex-list table))
这个push-if 被定义为一个宏,它使用<place> 表单参数上的get-setf-expansion 函数来获取生成代码以访问该位置一次所需的部分。
生成的代码评估一个加载表单以从该位置获取旧值,然后将条件应用于旧值,如果成功,则在从get-setf-expansion获得的适当临时存储变量中准备新值并评估商店形式。
这是您在可移植 Lisp 中可以做的最好的事情,您可能会发现它仍然执行两个散列操作,如上所述。 (在这种情况下,您希望哈希表本身有一个不错的缓存优化。但至少它减少到两个操作。)
该方法将与内置的变异形式一样优化:incf、push、rotatef 等。我们的push-if 将与内置函数相提并论。
如果它仍然很糟糕(执行两个哈希来更新一个哈希位置,没有缓存优化),那么解决这个问题的唯一方法是在实现级别。
push-if 代码如下:
(defmacro push-if (new-value predicate-fun list-place &environment env)
(multiple-value-bind (temp-syms val-forms
store-vars store-form access-form)
(get-setf-expansion list-place env)
(let ((old-val (gensym)))
(when (rest store-vars)
(error "PUSH-IF: cannot take ref of multiple-value place"))
`(multiple-value-bind (,@temp-syms) (values ,@val-forms)
(let ((,old-val ,access-form))
(when (funcall ,predicate-fun ,old-val)
(setf ,(first store-vars) (cons ,new-value ,old-val))
,store-form))))))
样本扩展:
> (macroexpand '(push-if new test place))
(LET* ((#:VALUES-12731 (MULTIPLE-VALUE-LIST (VALUES))))
(LET ((#:G12730 PLACE))
(WHEN (FUNCALL TEST #:G12730) (SETF #:NEW-12729 (CONS NEW #:G12730))
(SETQ PLACE #:NEW-12729)))) ;
当地点是一个变量时,对于简单的情况看起来很正常。只有一个小问题我不打算解决:new、test 和 place 表单每个只评估一次,但不是按从左到右的顺序!
使用哈希表位置 (CLISP) 进行测试:
> (macroexpand '(push-if new test (gethash a b)))
(LET*
((#:VALUES-12736 (MULTIPLE-VALUE-LIST (VALUES A B)))
(#:G12732 (POP #:VALUES-12736)) (#:G12733 (POP #:VALUES-12736)))
(LET ((#:G12735 (GETHASH #:G12732 #:G12733)))
(WHEN (FUNCALL TEST #:G12735) (SETF #:G12734 (CONS NEW #:G12735))
(SYSTEM::PUTHASH #:G12732 #:G12733 #:G12734)))) ;
啊哈;现在生成了一些更有趣的代码,以避免对a 和b 进行两次评估。 gethash 函数被调用一次,但它的参数是 gensym 变量。旧值被捕获为#:G12735。对其应用测试,如果通过,则存储变量 #:G12734 将更新为旧列表值,并在其前面加上 new。然后,将该值放入带有system::puthash 的哈希表中。
所以在这个 Lisp 实现中,没有办法避免两个哈希表操作来执行更新:gethash 和 system::puthash。这是我们能做的最好的事情,希望这两者可以作为优化的一对。