如果我在Common Lisp映像中托管长时间运行的应用程序(如Web服务器),我应该使用什么策略来管理垃圾收集器?
我假设,默认情况下,垃圾收集器有权花费很长时间来整理堆,有时我无法预测。这可能会以我不想要的方式影响特定的浏览器请求。
Common Lisp中有一个方法来控制它吗?也许通过鼓励它以“小而频繁”的方式工作?
答案 0 :(得分:25)
几个Lisp实现都有出色的垃圾收集器。一个特殊的问题是Lisp应用程序通常具有较高的小对象分配率(conses,...)。
有几件事需要考虑。
精确与保守的GC。对于Lisp,我不是保守的GC(Boehm等)的忠实粉丝。保守的GC的一个问题是它们找不到所有垃圾。对于长时间运行的程序而言,这可能是一个问题,并导致碎片而不是回收未使用的内存。精确GC使用Lisp数据的标记信息,可以识别每个对象的每种数据类型。保守的GC是为不使用标记数据的编程语言实现而发明的(C ++,...)。
复制GC,压缩GC。为了在长时间运行的Lisp中处理内存碎片,压缩和本地化对象的GC可能很有用。当哈希表需要重新哈希时(因为位置发生变化),有时会出现问题。复制GC可能需要更多内存(因为存在内存和内存空间)。但是当GC将对象从一个内存空间复制到另一个内存空间时,它会自动使其更紧凑。更高级的GC(比如在Lisp机器上)也可以对对象进行排序并将相同类型的对象分配到彼此附近 - 假设这将加速访问对象。
短暂的GC。这意味着第一个GC阶段专门在主存储器中运行,并从内存管理单元获得一些支持,以识别已更改的内存区域。扫描主存储器比扫描虚拟存储器快,扫描仅更改的存储器区域进一步减少了工作量。当分配了大量对象并迅速变成垃圾时,这会导致GC暂停非常短暂。
世代GC。通常现在的GC都是世代相传的。有一代以上的产品,在少数GC中存活的对象被提升为老一代。通常只有第一代经常使用GC。
调整。例如,LispWorks和Allegro CL的GC有很多调音旋钮。特别是对于长期运行的应用程序,阅读手册是有意义的,例如调整代数,大小和其他事物。
虚拟内存。 GC通过虚拟内存通常很慢。尽可能避免 - 为机器添加更多RAM。
手动内存管理。例如,CL-HTTP Web服务器使用资源进行一些手动内存管理。这些是预先分配的对象池,可以非常快速地重新初始化。 Lisp机器正在使用它。它们的典型用途是在流的读缓冲区中。不是每次读取操作都创建新的字符串,而是使用可重用的缓冲区。
堆栈分配。一些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
答案 1 :(得分:2)
从早期开始,垃圾收集已经走过了漫长的道路,并且已经做了很多工作来避免不可预测的漫长等待。对于现代实现,我认为这些已成为过去。
但是,垃圾收集的细节确实因实现而异。那里没有那么多高质量的Lisp实现,所以你应该毫不费力地查阅垃圾收集的文档。