13

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

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

Common Lisp 中是否有方法来控制它?也许通过鼓励它以“很少和经常”的方式工作?

4

2 回答 2

25

一些 Lisp 实现具有出色的垃圾收集器。一个特殊的问题是 Lisp 应用程序通常对小对象(conses,...)有很高的分配率。

有几件事需要考虑。

  • 精确与保守 GC。我不是 Lisp 的保守 GC(Boehm 等)的忠实粉丝。保守 GC 的一个问题是它们无法找到所有垃圾。对于长时间运行的程序来说,这可能是一个问题,并导致碎片和未回收的未使用内存。精确 GC 使用 Lisp 数据的标记信息,可以识别每个对象的每种数据类型。保守的 GC 是为不使用标记数据(C++,...)的编程语言实现而发明的。

  • 复制GC,压缩GC。为了防止长时间运行的 Lisps 中的内存碎片,压缩和本地化对象的 GC 可能很有用。当需要重新散列哈希表(因为位置发生变化)时,有时会出现问题。复制 GC 可能需要更多内存(因为内存有一个 from 和一个 to 空间)。但是当 GC 将对象从一个内存空间复制到另一个内存空间时,它会自动使其更紧凑。更高级的 GC(如在 Lisp 机器上)还可以对对象进行排序并在彼此附近分配相同类型的对象 - 假设这将加快访问对象的速度。

  • 临时GC。这意味着第一个 GC 阶段专门在主内存中运行,并从内存管理单元获得一些支持,以识别更改的内存区域。扫描主内存比扫描虚拟内存要快,并且只扫描更改的内存区域可以进一步减少工作量。当大量对象被分配并迅速变成垃圾时,这会导致非常短的 GC 暂停。

  • 代GC。现在的 GC 通常是分代的。有不止一代,并且在几次 GC 中幸存下来的对象被提升为老一代。通常只有第一代经常被 GC。

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

  • 虚拟内存。虚拟内存上的 GC 通常非常慢。如果可能,请避免这种情况 - 向机器添加更多 RAM。

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

  • 堆栈分配。一些 Common Lisp 允许堆栈分配一些数据 - 离开块然后自动释放内存。这假设在离开块时不再引用内存。请参阅声明dynamic-extent

  • 并发GC。通常的 Common Lisp 实现都没有并发 GC 和对并发 Lisp 线程的支持。一些实现有并发的 Lisp 线程,但是 GC 会在它们工作时停止它们。如果 Lisp 实现在 JVM 上运行,比如 ABCL,那么它可能能够使用 JVM 并发/并行 GC。

  • 分析内存分配。如果您不确定分配发生在哪里以及 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
于 2009-04-13T17:58:27.923 回答
2

自早期以来,垃圾收集已经走过了漫长的道路,并且已经做了很多工作来避免不可预测的长时间等待。对于现代实现,我认为这些已经成为过去。

但是,垃圾收集的细节确实因实现而异。那里没有那么多高质量的 Lisp 实现,所以你应该可以毫不费力地查阅他们关于垃圾收集的文档。

于 2009-04-13T14:06:14.923 回答