Lisp列表是否始终作为链接列表实现?

时间:2015-05-17 04:05:29

标签: linked-list lisp cpu-cache

Lisp列表是否总是作为链接列表实现?

就处理器缓存而言,这是一个问题吗?如果是这样,是否存在使用更多连续结构来帮助缓存的解决方案?

5 个答案:

答案 0 :(得分:6)

链接对是通常的实现,但过去还有其他方法。

CDR编码是一种列表压缩方案,旨在改善某些Lisp机器上硬件支持的cons列表的连续性和数据大小。基本思想是使用标签来表示缺点的形状:其中一种可能性是直接在第一个元素之后存储下一个缺点,基本上忽略了cdr字段。

下一个缺点本身可以​​以相同的方式进行压缩,因此在有利的情况下,您最终会得到一个具有极佳邻接的阵列式结构。 (它不是一个数组,因为标记信息必须有空格,你不能将其编入索引。)

其中一个棘手的部分是有效支持压缩后的carcdr突变。 (参见Steele的论文," CDR编码列表的破坏性重新排序"。)如果conses是不可变的,则标记方案可以更简单。 This FAQ对权衡进行了一些有趣的讨论。

CDR编码的缺点是,由于缺点可能是各种不同的形状,因此列表操作需要在标签上进行调度。这引入了代码大小和分支错误预测成本。这些成本使得该功能的吸引力大大降低,以至于我不知道任何使用CDR编码的现代Lisp实现。

如果需要考虑邻接,Lisp程序员通常只会使用数组。

答案 1 :(得分:4)

Lisp实现通常可以直接在cons单元格中存储一些值:fixnums,characters,......对于其他所有内容,指针将存储在carcdr中。

现在几乎所有使用cons单元 的实现都不会使用像cdr-coding这样的优化。

通常使用复制/压缩/分代垃圾收集器来改进内存局部性。

  • 复制 - >当空间已满时,GC会复制列表并在新存储区域中将新单元格分配到彼此

  • 压缩 - >一些摆脱记忆差距或类似的方案

  • 世代 - >较长的生物物体将被提升到不同的存储区域。因此,在某些GC中幸存下来的列表将被复制到另一代,并且这些单元格将彼此相邻分配。

有时,上面的GC连续性会以奇特的方式结合起来。

另请注意,在许多Lisp程序中,许多缺陷单元可能是短暂的:

(mapcar #'1+
        (mapcar #'isqrt '(10 20 30 40 50))  ; <- result is 'garbage'
        )

整数平方根列表立即是垃圾。该函数将遍历新鲜的cons细胞并分配新的cons cons细胞,并且不会有很多缓存非局部性。

通过使用破坏性操作可以减少cons单元的分配。以上可以写成:

CL-USER 24 > (let ((s (mapcar #'isqrt '(10 20 30 40 50))))
               (map-into s #'1+ s))
(4 5 6 7 8)

这将删除一个已分配的列表并进一步改善位置。

答案 2 :(得分:3)

Rainer已经提到各种内存管理技术有助于本地化。我想用SBCL展示两个实验来说明他的观点。

首先,快速实用程序在列表中打印每个缺点的地址。

(defun print-addresses (list)
  (mapl (lambda (cons)
          (format t "address: 0x~X~%"
                  (sb-kernel:get-lisp-obj-address cons)))
        list))

在第一个实验中,我们可以看到分配是连续的,因此我们可以创建一个包含十个元素的列表,并且在它们的原始地址处查看它们会显示它们一起关闭:

> (print-addresses (loop repeat 10 collect 'dummy))
address: 0x1003F57167
address: 0x1003F57177
address: 0x1003F57187
address: 0x1003F57197
address: 0x1003F571A7
address: 0x1003F571B7
address: 0x1003F571C7
address: 0x1003F571D7
address: 0x1003F571E7
address: 0x1003F571F7

第二次实验。如果我们之间进行一些不相关的分配怎么办?让我们将这样一个列表分配给一个变量,以便我们以后可以戳它。

(defparameter *another-list*
  (loop repeat 10
        ;; using eval to trick the compiler into
        ;; compiling this piece of dummy code
        do (eval '(make-array (random 1000)))
        collect 'dummy))

我们可以看到这次地址更随机:

> (print-addresses *another-list*)
address: 0x10046E9AF7
address: 0x10046EB367
address: 0x10046ECB97
address: 0x10046EE827
address: 0x10046EF247
address: 0x10046F1F17
address: 0x10046F2007
address: 0x10046F3FD7
address: 0x10046F5E67
address: 0x10046F6887

现在,如果我们使用(sb-ext:gc)调用GC,我们可以看到它已将所有内容打包在一起:

> (sb-ext:gc)
> (print-addresses *another-list*)
address: 0x1004738007
address: 0x1004738017
address: 0x1004738027
address: 0x1004738037
address: 0x1004738047
address: 0x1004738057
address: 0x1004738067
address: 0x1004738077
address: 0x1004738087
address: 0x1004738097

在这些例子中,我们没有评估列表元素的位置,我想这是另一天的实验。 : - )

答案 3 :(得分:3)

哲学&#34;权利&#34;答案是&#34; Lisp没有列表,只有CONSes&#34;。 Conses通常用于构建列表,以至于CL标准和库中的许多函数都在这些类型的列表上运行。但是,也可以使用conses来构建其他类型的结构,例如地图或图形。所以在(传统的)Lisps中,基本数据结构是缺点,而不是列表。该列表只是缺点的一个方便应用。因此,通过&#34; Lisp列出&#34;你是真正的意思&#34;用Lisp conses实现的列表&#34;那些,不能用与conses不同的东西来实现;)

当然,在其他答案中提到的像CDR编码这样的技术可以用来有效地表示某些基于cons的结构。还有一些库提供了不基于链接的conses的列表数据结构(如Common Lisp的FSet)。

对于#34;传统&#34; Lisp,如Common Lisp和Scheme。 Clojure确实将列表作为基本数据类型,AFAIK根本没有说明。

答案 4 :(得分:1)

据我了解,Clojures&#39;序列实现为VLists。是的,list通常是Lisp中的链接列表(尽管我确定这是一个或两个使用其他东西的实验性构建)。