给定一个常量值列表,什么是最有效的查找?

问题描述 投票:0回答:1

您有一个在编译时已知的键值对列表。我们不想在运行时查找,而是希望生成一个查找表。例如,我们可以这样做(来自我昨天写的一段代码):

(defun mod-mask (char)
  (case char
    (#\A #.(ash 1 22))
    (#\s #.(ash 1 23))
    (#\H #.(ash 1 24))
    (#\S #.(ash 1 25))
    (#\C #.(ash 1 26))
    (#\M #.(ash 1 27))))

(defun mask-mod (mask)
  (case mask
    (#.(ash 1 22) #\A)
    (#.(ash 1 23) #\s)
    (#.(ash 1 24) #\H)
    (#.(ash 1 25) #\S)
    (#.(ash 1 26) #\C)
    (#.(ash 1 27) #\M)))

但是你不想重复自己,而每个人都明白它的作用的整洁、简单的代码真的很无聊!

开个玩笑,上面的例子很短,但是可以有很多选择,而且数量非常多。我们不想输入太多内容,因为这样很容易出错而且很乏味。此外,列表还具有在其他地方查找的优点:例如,我们可以看到定义了哪些元素,而我们无法从编译的案例形式中获取哪些元素。与手动输入 case 语句不同,列表以及代码也可以在运行时生成。

我们制作一个列表来代替上面的:

(defvar *modifier-masks*
  `((#\A . ,(ash 1 22))
    (#\s . ,(ash 1 23))
    (#\H . ,(ash 1 24))
    (#\S . ,(ash 1 25))
    (#\C . ,(ash 1 26))
    (#\M . ,(ash 1 27))))

并创建一个宏来生成以上两种情况:

(defmacro mod-lookup-gen (&optional bit)
  `(lambda (mask)
     (case mask
       ,@(mapcar (lambda (e)
                   (if bit
                       `(,(car e) ,(cdr e))
                       `(,(cdr e) ,(car e))))
          *modifier-masks*))))

我们可以在简单的包装中使用它:

(defun mask-mod (mask)
  (let ((get-char (mod-lookup-gen)))
    (funcall get-char mask)))

(defun mod-mask (modifier)
  (let ((get-char (mod-lookup-gen 'bit)))
    (funcall get-char modifier)))

问题:什么是更好的选择,以及更高效的编译时生成代码,相当于上面的两个 defun?有没有办法摆脱 lambda 包装 case 语句以跳过额外的函数调用?除了可能内联它之外?

一般来说,在 Common Lisp 中查找键值对最有效的方法是什么?由于编译器可以在编译时布置用于查找的静态代码,因此“switch”表达式是查找值的最快方法吗?我想这取决于实施,但也许可以提供一些一般性指导?

还要注意,hashmap 和 alist 之间的区别在于,alist 以及由此生成的 switch 语句关心语句的顺序,即它们按照声明的顺序进行匹配,而 hashmap 是无序查找。这可能重要也可能不重要,具体取决于用例。

顺便说明一下,这是“枚举”惯用语。我见过一些用于生成枚举的 CL 库,但我没有使用过任何库,所以我不知道它们生成什么样的代码。也许有人可以推荐一个可以生成良好的编译时查找的工具?

common-lisp ansi-common-lisp
1个回答
0
投票

这个问题的答案是:编写代码并测量它。 (也许)很明显,在很多键的限制情况下,基于哈希表的东西会比基于

case
的东西更快。 但断点在哪里取决于实现的细节。 当然,如果愿意的话,足够激进的编译器总是可以将
case
变成哈希表。

对于您的示例,我编写了下面的代码,我发现,在一种实现中,

case
实现比哈希表快几倍,而 alist 则介于两者之间。 在另一种实现中,
case
实现速度最快,哈希表速度较慢,alist 速度最慢。

如果您想了解有关性能的信息,则必须测量

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defvar *modifier-masks*
    `((#\A . ,(ash 1 22))
      (#\s . ,(ash 1 23))
      (#\H . ,(ash 1 24))
      (#\S . ,(ash 1 25))
      (#\C . ,(ash 1 26))
      (#\M . ,(ash 1 27)))))

(declaim (inline modifier-case-map mask-case-map))

(defun modifier-case-map (c)
  (declare (type character c))
  (macrolet ((cmap (v)
               `(ecase ,v
                  ,@(mapcar (lambda (m)
                              `(,(car m) ,(cdr m)))
                            *modifier-masks*))))
    (cmap c)))

(defun mask-case-map (m)
  (declare (type fixnum m))
  (macrolet ((mmap (v)
               `(ecase ,v
                  ,@(mapcar (lambda (m)
                              `(,(cdr m) ,(car m)))
                            *modifier-masks*))))
    (mmap m)))

(declaim (inline modifier-hash-map mask-hash-map))

(defun modifier-hash-map (c)
  (declare (type character c))
  (or (gethash c (load-time-value (let ((h (make-hash-table
                                            :size (length *modifier-masks*))))
                                    (dolist (m *modifier-masks* h)
                                      (setf (gethash (car m) h) (cdr m))))))
      (error "not present")))

(defun mask-hash-map (m)
  (declare (type fixnum m))             ;?
  (or (gethash m (load-time-value (let ((h (make-hash-table
                                            :size (length *modifier-masks*))))
                                    (dolist (m *modifier-masks* h)
                                      (setf (gethash (cdr m) h) (car m))))))
      (error "not present")))

(declaim (inline modifier-alist-map mask-alist-map))

(defun modifier-alist-map (c)
  (declare (type character c))
  (or (cdr (assoc c (load-time-value *modifier-masks* t)))
      (error "not present")))

(defun mask-alist-map (m)
  (declare (type fixnum m))             ;?
  (or (gethash m (load-time-value *modifier-masks* t))
      (error "not present")))

;;; All the setfery is to try to make sure things do not get optimized
;;; away

(defun tsc (n)
  (let ((r nil))
    (dotimes (i n r)
      (dolist (c (load-time-value (mapcar #'car *modifier-masks*)))
        (setf r (modifier-case-map c))))))

(defun tsh (n)
  (let ((r nil))
    (dotimes (i n r)
      (dolist (c (load-time-value (mapcar #'car *modifier-masks*)))
        (setf r (modifier-hash-map c))))))
  
(defun tsa (n)
  (let ((r nil))
    (dotimes (i n r)
      (dolist (c (load-time-value (mapcar #'car *modifier-masks*)))
        (setf r (modifier-alist-map c))))))
© www.soinside.com 2019 - 2024. All rights reserved.