【问题标题】:Avoiding redundant place calculations with setf使用 setf 避免多余的位置计算
【发布时间】:2014-02-25 07:59:47
【问题描述】:

背景

根据我对 Common Lisp 的有限经验,我发现这样的代码并不少见

(setf (gethash key table)
      (my-transformation (gethash key table)))

您从setfable 位置读取的位置,对存储在那里的值执行一些计算,然后写入同一个位置。我不喜欢这个地方的地方将被计算两次,但地方(希望!)两次都是一样的。如果位置计算成本很高,我们所做的工作量是我们需要的两倍。

问题

是否可以消除双重计算的需要?也就是说,是否可以(可取?)编写宏setf-inplace

(setf-inplace (gethash key table) #'my-transformation)

与原始代码在概念上等效(暂时忽略 multiple-values 怪异)但可能比原始代码快得多,最好不依赖实现细节?

我知道在这种特殊情况下我可能会把车放在马的前面,因为SBCL caches the lookup for gethash,但在我看来,其他setf 可能的地方,比如(assoc key my-alist),可能并不那么容易缓存——当然,除了通过上面的setf-inplace 之类的机制。

【问题讨论】:

    标签: common-lisp


    【解决方案1】:
    (setf (gethash key table)
          (my-transformation (gethash key table)))
    

    假设转换是1+。那么上面是

    (setf (gethash key table)
          (1+ (gethash key table)))
    

    Common Lisp 里面有一个宏:

    (incf (gethash key table))
    

    Clozure CL 将其扩展为:

    (LET* ((#:G69020 KEY)
           (#:G69021 TABLE)
           (#:G69022 1)
           (#:G69019 (+ (GETHASH #:G69020 #:G69021) #:G69022)))
      (DECLARE (TYPE BIT #:G69022) (TYPE T #:G69019))
      (CCL::PUTHASH #:G69020 #:G69021 #:G69019))
    

    现在请记住,位置的 setter 是在编译时找到的,并且位置的 setter 操作与 getter 不同。 setter 不仅通过更新某些东西来做一些不同的事情,而且它还可以做任意其他事情,例如设置事务、获取锁……因此,getting 和 setting 不是两次相同/相似的操作。

    Common Lisp 没有表示地点的物理数据结构。一个地方是一个抽象的概念,它结合了一个 setter 和一个给定的 getter。 setter 可以是任意复杂的代码。

    那么,在上面的代码中 - 哪些计算是多余的?

    你还是说如何优化原始情况的访问:

    (incf (car (very-long-access-chain data)))
    

    你必须手写:

    (let ((cons-cell (very-long-access-chain data)))
      (incf (car cons-cell))
    

    这基本上就是 Lisp 所做的。见:CLHS5.1.1.1 Evaluation of Subforms to Places

    【讨论】:

    • 在上一个示例中,将超长访问链从 incf 移到 let 中没有任何好处,在我(和我的宏扩展器)看来,唯一的区别两种形式之间是一个额外的词法绑定。我错过了重点吗?
    • 有道理;我没有意识到 getter 和 setter 可能是不相关的计算。事实上,看看 SBCL 的 SB!IMPL:GETHASH 等,实现之间共享的代码并不多。尽管如此,在我看来,例如,应该可以定义一个 setf-expander 将“位置”缓存在其临时变量中。在这种情况下,setf-inplace 这样的宏应该是可行的。不过,我不知道是否有可能编写它来使用非缓存 setf-expanders 做任何明智的事情。
    • @Stuart Olsen:你找不到地方。它不存在。位置不是您可以存储的指针或数据结构。这就是问题所在。
    • @Stuart Olsen:一对不是必须存储引用的结构。 (cons 3 nil)。这三个不是参考。 (let ((foo some-object)) (list (cons foo nil) (cons foo nil)))。如果 some-object 是一个固定编号,它将被内联存储。如果 some-object 是一个数组,则将存储一个引用,并且两个引用都指向同一个对象。 cons 也不能指向哈希表、复数、向量、字符串,也不能指向抽象对象,...
    • setf 机制很酷,但我认为它不能满足 OP 的要求。考虑 (incf (getf :key place 0))。据我所知,不可能为 getf 编写 setf 方法,以避免在 value 中搜索属性列表两次。同时 setf 不是线程安全的,所以虽然这很有趣,但会分散注意力。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-20
    • 1970-01-01
    • 1970-01-01
    • 2020-01-19
    • 2020-12-27
    • 2018-06-22
    • 1970-01-01
    相关资源
    最近更新 更多