【问题标题】:In a long running Common Lisp application, what strategy should be used to manage garbage?在长时间运行的 Common Lisp 应用程序中,应该使用什么策略来管理垃圾?
【发布时间】:2024-05-22 02:05:01
【问题描述】:

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

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

Common Lisp 中是否有方法来控制它?也许是通过鼓励它以“偶尔”的方式工作?

【问题讨论】:

  • 你使用的是哪个 lisp 实现?
  • 当您说“我假设”时,听起来您正在尝试进行优化而没有进行任何分析。您是否看到需要在您的环境中解决的特定问题?
  • 我开始向自己介绍尚未出现的问题。考虑到 Web 服务上使用的 GC 语言的数量,我认为这是一个很大程度上已解决的问题。
  • 哦,我暂时不尝试优化任何东西。我只是想了解当/如果问题确实出现时我的选择是什么。

标签: web-services garbage-collection lisp common-lisp


【解决方案1】:

一些 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 服务器使用 resources 进行一些手动内存管理。这些是可以非常快速地重新初始化的预分配对象池。 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

【讨论】:

  • 我认为您对标签使用的陈述并不完全正确。精确的 GC 确实会通过查看对象的标记来决定如何清理(不精确的也会如此),但首先它必须知道寄存器的内容是对象指针还是立即值。后一种操作将精确 GC 与不精确 GC 区分开来。
  • 获取一个包含不同事物的数组:直接对象(数字、字符、...)和非立即对象(数组包含指向它们的指针:数组、结构、字符串...) . GC 是如何知道一件事是数字而另一件事是指针(指向结构)? Conservative GC 是为 C 和 C++ 等没有标记对象的语言开发的。 GC 启发式地确定什么可以是指针,什么不是。
  • 它可能是为像 C 这样的无标签语言开发的,但它甚至在今天的一些 Lisp 环境中也被使用,例如,CMUCL 和 SBCL 在 x86 等“注册不良”架构上使用保守的 GC:sbcl-internals.cliki.net/GENCGC
【解决方案2】:

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

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

【讨论】:

    最近更新 更多