如果我正在托管长时间运行的应用程序,比如一个Web服务器,在Common Lisp镜像中,那么我应该使用什么策略来管理垃圾收集器?
我假设默认情况下,垃圾收集器有权花费长时间来整理堆,而在我无法预测的时候。这可能会以我不想要的方式影响特定的浏览器请求。
在Common Lisp中是否有一种方法来控制这个问题?也许是通过鼓励它以“少量而经常”的方式工作?
如果我正在托管长时间运行的应用程序,比如一个Web服务器,在Common Lisp镜像中,那么我应该使用什么策略来管理垃圾收集器?
我假设默认情况下,垃圾收集器有权花费长时间来整理堆,而在我无法预测的时候。这可能会以我不想要的方式影响特定的浏览器请求。
在Common Lisp中是否有一种方法来控制这个问题?也许是通过鼓励它以“少量而经常”的方式工作?
许多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。
分析内存分配。如果您不确定分配发生在哪里
指针:
您可以从文档中看到,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
自从垃圾回收技术问世以来,它已经取得了长足的进步,并且已经有很多工作致力于避免不可预测的长时间等待。对于现代实现来说,我认为这些问题已经成为过去。
然而,不同的实现方式会影响垃圾回收的细节。高质量的Lisp实现并不多,因此您可以轻松查阅它们的垃圾回收文档。