在长时间运行的Common Lisp应用程序中,应该使用什么策略来管理垃圾?

14

如果我正在托管长时间运行的应用程序,比如一个Web服务器,在Common Lisp镜像中,那么我应该使用什么策略来管理垃圾收集器?

我假设默认情况下,垃圾收集器有权花费长时间来整理堆,而在我无法预测的时候。这可能会以我不想要的方式影响特定的浏览器请求。

在Common Lisp中是否有一种方法来控制这个问题?也许是通过鼓励它以“少量而经常”的方式工作?


你使用的是哪个 Lisp 实现? - Zifre
当你说“我假设”的时候,听起来像是你在没有进行任何分析的情况下尝试进行优化。你是否在你的环境中看到需要修复的具体问题? - Ken
我正在开始自我了解尚未出现的问题的过程。考虑到网络服务中使用的GC语言数量,我认为这是一个基本上已经解决的问题。 - John McAleely
哦,我现在并不打算优化任何东西。我只是想尝试了解一下如果问题出现时我的选择是什么。 - John McAleely
2个回答

26

许多Lisp实现都有出色的垃圾回收器。一个特殊的问题是,Lisp应用程序通常具有高分配速率的小对象(conses等)。

有几件事需要考虑。

  • Precise vs. Conservative GC. 我不太喜欢保守型GC(如Boehm等)用于Lisp。保守型GC的问题是它们不能找到所有的垃圾。这对于长时间运行的程序可能会导致碎片化和未被回收的未使用内存。精确GC使用Lisp数据的标签信息,可以识别每个对象的每种数据类型。保守型GC是为那些不使用带标签数据(如C++等)的编程语言实现而发明的。

  • copying GC,compacting GC。为了解决长时间运行的Lisp中的内存碎片问题,一个能够压缩和本地化对象的GC很有用。有时候会出现问题,比如哈希表需要重新哈希(因为位置改变)。复制GC可能需要更多的内存(因为有一个内存空间的源和目标)。但是当GC将对象从一个内存空间复制到另一个内存空间时,它会自动使其更加紧凑。更先进的GC(如Lisp Machine上的GC)还可以对对象进行排序,并将相同类型的对象分配在彼此附近-假设这将加速访问对象。

  • ephemeral GC。这意味着有一个第一阶段的GC专门在主内存中运行,并得到内存管理单元的支持来识别已更改的内存区域。扫描主内存比扫描虚拟内存快,仅扫描更改的内存区域可以进一步减少工作量。当分配了大量对象并且快速变成垃圾时,这会产生非常短的GC暂停。

  • generational GC。通常现代GC是分代的。有多个代,经过几次GC后生存的对象会被提升到更旧的一代。通常只有第一代经常进行GC。

  • 调整。例如LispWorks和Allegro CL的GC有很多调整选项。特别是对于长时间运行的应用程序,阅读手册并调整代数的数量、它们的大小和其他事项是有意义的。

  • 虚拟内存。在虚拟内存上进行GC通常非常慢。如果可能的话,请避免这样做-向机器添加更多RAM。

  • manual memory management。例如,CL-HTTP Web服务器使用resources进行一些手动内存管理。这些是预先分配的对象池,可以非常快速地重新初始化。Lisp Machines经常使用这种方法。它们的典型用途是流的读缓冲区。与每次读取操作创建新字符串不同,使用可重用的缓冲区是有用的。

  • 堆栈分配。一些Common Lisp允许对某些数据进行堆栈分配-离开块时自动释放内存。这就假设在离开块时不再引用该内存。请参见声明dynamic-extent

  • concurrent GC。通常的Common Lisp实现都没有并发GC和支持并发Lisp线程的功能。一些实现具有并发Lisp线程,但GC将停止它们所有的工作。如果Lisp实现运行在JVM上,如ABCL,则可能能够使用JVM并发/并行GC。

  • 分析内存分配。如果您不确定分配发生在哪里

    如果你的Lisp有一个在主内存中运行的精确分代GC,那么很难出现长时间暂停的问题。例如,Clozure CL(一个免费的Common Lisp实现)在某些平台上具有非常好的GC实现。您要避免在虚拟内存中发生内存碎片和垃圾收集。如果必要,使用一个64位的Lisp实现来增加主内存。

    指针:

    您可以从文档中看到,LispWorks和Allegro CL有很多用于调整GC的旋钮。

    Common Lisp有一些处理实现环境的函数。 (ROOM)是一个给出内存使用概述的函数。 (ROOM t) 给出更详细的信息(这里是LispWorks):

    CL-USER 2 > (room t)
     Generation 0:  Total Size 1823K, Allocated 1090K, Free 725K 
              Segment 2008AAB8: Total Size 507K, Allocated 361K, Free 142K
                        minimum free space 64K, 
                          Awaiting promotion = 0K, sweeps before promotion =10
              Segment 217E7050: Total Size 1315K, Allocated 729K, Free 582K
                        minimum free space 0K, 
                          Awaiting promotion = 0K, sweeps before promotion =2
     Generation 1:  Total Size 1397K, Allocated 513K, Free 871K 
              Segment 20CB9A50: Total Size 68K, Allocated 48K, Free 15K
                        minimum free space 0K, 
                          Awaiting promotion = 0K, sweeps before promotion =4
              Segment 216D7050: Total Size 1088K, Allocated 331K, Free 752K
                        minimum free space 0K, 
                          Awaiting promotion = 0K, sweeps before promotion =4
              Segment 2004E4F8: Total Size 241K, Allocated 133K, Free 103K
                        minimum free space 0K, static
     Generation 2:  Total Size 2884K, Allocated 1290K, Free 1585K 
              Segment 21417050: Total Size 2816K, Allocated 1227K, Free 1584K
                        minimum free space 0K, 
                          Awaiting promotion = 0K, sweeps before promotion =4
              Segment 20DA5DA0: Total Size 68K, Allocated 62K, Free 1K
                        minimum free space 117K, 
                          Awaiting promotion = 0K, sweeps before promotion =4
     Generation 3:  Total Size 19373K, Allocated 19232K, Free 128K 
              Segment 20109A50: Total Size 11968K, Allocated 11963K, Free 0K
                        minimum free space 3K, 
                          Awaiting promotion = 0K, sweeps before promotion =10
              Segment 20DB6E18: Total Size 6528K, Allocated 6396K, Free 128K
                        minimum free space 0K, 
                          Awaiting promotion = 0K, sweeps before promotion =10
              Segment 20CCAAC8: Total Size 876K, Allocated 872K, Free 0K
                        minimum free space 0K, 
                          Awaiting promotion = 0K, sweeps before promotion =10
    
    Total Size 25792K, Allocated 22127K, Free 3310K
    

我认为你对标签使用的陈述并不完全正确。确实,精确 GC 会根据对象的标签来决定如何清理(与不精确 GC 相同),但首先它必须知道寄存器内容是对象指针还是立即值。这后者的操作是区分精确和不精确 GC 的关键。 - Leslie P. Polzer
1
取一个包含不同东西的数组:即时对象(数字,字符,...)和非即时对象(数组、结构、字符串等,数组保存了指向它们的指针)。垃圾回收器如何知道一件事是数字而另一件事是指针(指向结构体)?保守型垃圾回收器是为像C和C++这样没有标记对象的语言开发的。垃圾回收器通过启发式确定什么可能是指针,什么不是指针。 - Rainer Joswig
它可能是为像C这样的未标记语言开发的,但今天甚至在一些Lisp环境中也被使用,例如CMUCL和SBCL在“寄存器贫乏”的架构(如x86)上使用保守GC:http://sbcl-internals.cliki.net/GENCGC - Ken

2

自从垃圾回收技术问世以来,它已经取得了长足的进步,并且已经有很多工作致力于避免不可预测的长时间等待。对于现代实现来说,我认为这些问题已经成为过去。

然而,不同的实现方式会影响垃圾回收的细节。高质量的Lisp实现并不多,因此您可以轻松查阅它们的垃圾回收文档。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接