Lisp 为什么口齿不清的思考很慢?

Lisp 为什么口齿不清的思考很慢?,lisp,common-lisp,Lisp,Common Lisp,我在《关于Lisp》一书中读到,应该避免在扩展宏的主体中过度使用cons。为什么cons被认为是低效的操作?Lisp不与cons单元格共享结构吗?我认为这并不慢。它不会创建整个列表的新副本,它只是在链接列表的前面添加一个新元素 如果速度慢,那就是它必须为新节点分配一些内存。如果要创建大量节点,最好一次创建所有节点,而不是一个接一个地创建。事实上,考虑在良好的实现中相当快,但是可以通过避免它来获得更好的性能。如果您更改的内容是您自己新创建的,则可以安全地使用破坏性操作,如本例所示: CL-USER

我在《关于Lisp》一书中读到,应该避免在扩展宏的主体中过度使用
cons
。为什么
cons
被认为是低效的操作?Lisp不与cons单元格共享结构吗?

我认为这并不慢。它不会创建整个列表的新副本,它只是在链接列表的前面添加一个新元素


如果速度慢,那就是它必须为新节点分配一些内存。如果要创建大量节点,最好一次创建所有节点,而不是一个接一个地创建。

事实上,考虑在良好的实现中相当快,但是可以通过避免它来获得更好的性能。如果您更改的内容是您自己新创建的,则可以安全地使用破坏性操作,如本例所示:

CL-USER> (defun non-destructive ()
           (progn
             (reverse (loop for x from 1 to 100000 collecting x))
             nil))
NON-DESTRUCTIVE
CL-USER> (defun destructive ()
           (progn
             (nreverse (loop for x from 1 to 100000 collecting x))
             nil))
DESTRUCTIVE
CL-USER> (time (non-destructive))
(NON-DESTRUCTIVE) took 140 milliseconds (0.140 seconds) to run 
                    with 2 available CPU cores.
During that period, 156 milliseconds (0.156 seconds) were spent in user mode
                    0 milliseconds (0.000 seconds) were spent in system mode
94 milliseconds (0.094 seconds) was spent in GC.
 1,600,024 bytes of memory allocated.
NIL
CL-USER> (time (destructive))
(DESTRUCTIVE) took 93 milliseconds (0.093 seconds) to run 
                    with 2 available CPU cores.
During that period, 93 milliseconds (0.093 seconds) were spent in user mode
                    0 milliseconds (0.000 seconds) were spent in system mode
63 milliseconds (0.063 seconds) was spent in GC.
 800,024 bytes of memory allocated.
NIL

所以:是的,避免考虑可以提高性能,但只有知道自己在做什么时,才应该使用破坏性修改。我不会说思考本身是“慢”的,但尽管如此,避免思考还是有益的。如果您比较分配内存的差异(这需要时间),那么应该很清楚为什么第二个版本比第一个版本快。

您需要首先理解术语

CONS是创建新CONS单元格的基本操作

考虑通常意味着在堆上分配内存,而不仅仅是限制单元:考虑数字、考虑数组、考虑CLOS对象

“内存管理词汇表”中的定义是:

  • cons(1)在Lisp中,cons是创建列表元素的基本操作(来自英文“CONStruct”)。通过扩展,cons是创建的元素
  • 缺点(2)(有关详细信息,请参阅分配)分配是一个过程 分配资源的方法。什么时候 根据程序的要求 应用程序内存管理器或 分配器分配一个数据块 用于存储程序的内存(2) 它的数据在。分配也很重要 被称为consing,来源于cons(1)
  • 所以,格雷厄姆使用了第二种含义

    如果格雷厄姆说“避免特别考虑”。不必要地消耗的实用程序可能会破坏其他高效程序的性能。“-这意味着:避免在堆上分配不必要的内存。但这对于任何代码都是正确的——宏、函数等等

    当计算机的内存更少,垃圾收集的成本更高时(特别是当需要扫描虚拟内存系统中的交换内存时,物理内存比虚拟内存小)

    今天,考虑不是一个问题,但仍然是相关的

    以函数READ-LINE为例,它从一个流中读取一行并“conses”一个新字符串。请注意,字符串不是由conses构建的,而是一个向量。这里的意思是“在堆上分配字符串”。如果您有一个包含很多行的大文件,那么使用一个例程获取缓冲区并用行字符填充缓冲区会更快(在公共Lisp中有一个带有填充指针的向量)。这样,行缓冲区就只是一个对象,并且可以潜在地用于调用该行读取函数


    请参阅Allegro CL文档中的这个示例:.

    Consing是动态内存分配的Lisp行话,它并不慢。但是它增加了程序的开销。您不能只考虑分配对象的成本,而是考虑生命周期的全部成本,从创建对象到在对象变成垃圾时回收它

    不必要的考虑对垃圾收集器“施加压力”;它使垃圾收集工作更加频繁

    (这不是一个LISP问题。例如,对<代码> MulcC:<代码>和<代码>免费< /代码>的调用的C程序,或使用<代码>新< /COD>和<代码>删除>代码>的C++程序,也将不执行类似的程序,这些程序被更好地组织起来以避免这些调用。 您不必担心避免在Lisp中考虑,因为这会给编程带来不便,而避免考虑的一些技巧,包括破坏性地重用现有对象,很容易出现bug


    但考虑到在执行了大量次的内部循环中,以及在低级别代码(如用于广泛重用的实用程序函数)中发生的情况,这是值得注意的。例如,假设
    mapcar
    以反向方式在内部构建结果列表,然后通过调用
    reverse
    而不是
    nreverse
    将其按正确顺序放置。然后,调用
    mapcar
    的所有内容都会受到不必要的考虑。

    您能在On-Lisp手册中提供一个章节参考吗?格雷厄姆写道,也许我们可以根据上下文来解释这句话的意思宏通常用于实现通用实用程序,然后在程序中到处调用。经常使用的东西不能被认为是低效的。一个看似无害的小宏,在所有调用扩展后,可能会占程序的很大一部分。这样一个宏应该得到比其长度似乎需要更多的关注。避免特别考虑。一个不必要地消耗的实用程序可能会破坏一个原本高效的程序的性能。这就是让我困惑的,应该是一个常数时间操作。在这种情况下,我有点不清楚为什么我不想在扩展的宏代码中经常使用cons。我仍然想知道为什么会出现这种情况。正如上面马克·拜尔斯所说的那样,这仅仅是内存分配的问题吗?我对Lisp实现的情况并不了解,但我要说的是:是的,分配和GCing就是它的问题。关于Lisp,1993年出版。今天的地面军事系统是光年,超出了实际执行的范围