Clozure CL中文版016:了解和配置垃圾收集器
Category:帮助手册了解和配置垃圾收集器
堆空间分配
Clozure CL的0.10或更高版本使用与先前版本不同的内存管理方案。那些早期版本将在启动时分配一块内存(指定大小),并在该块中分配lisp对象。当该块填充了实时(非GCed)对象时,lisp将发出“堆满”状态的信号。堆大小对可分配的最大对象的大小施加了限制。
新策略涉及在启动时保留一个非常大的(在DarwinPPC32上为2GB,在LinuxPPC上为1GB,在64位实现上为“非常大”)并在实时lisp堆数据的大小增加和缩小时消耗(并放弃)其内容。在初始堆映像加载之后以及每个完整GC之后,lisp内核将尝试确保可用内存的指定量(“lisp-heap-gc-threshold”)。这个内核变量的初始值在32位实现上是16MB,在64位实现上是32MB; 它可以从Lisp中操作(见下文)。
大型保留存储器块在系统资源方面消耗很少; 实际上提交给lisp堆的内存(实时数据和发生分配的“阈值”区域)消耗有限的资源(物理内存和交换空间)。lisp对这些资源的消耗与其实际内存使用量成正比,这通常是一件好事。
该方案比旧方案灵活得多,但也可能增加这些资源耗尽的可能性。新方案和老方案都没有优雅地处理这种情况; 在旧方案下,耗尽大量内存的程序可能在耗尽虚拟内存之前遇到堆大小的人为限制。
-R或-heap-reserve命令行选项可用于限制保留块的大小,从而限制堆扩展。运行
> openmcl –heap-reserve 8M
将提供与早期Clozure CL版本提供的执行环境非常相似的执行环境。
短暂的GC
对于许多程序,以下观察结果在很大程度上是正确的:
- 大多数堆分配的对象具有非常短的生命周期(“短暂”):它们在创建后很快就无法访问。
- 大多数非短暂的物体具有很长的使用寿命:GC很少考虑回收它们,因为它很少能够这样做。(在大量GC中幸存下来的对象很可能在下一个GC中存活下来。当然,这并不总是正确的,但这是一种合理的启发式方法。)
- 旧对象被破坏性地修改(通过SETF)相对罕见,因此它指向一个新对象,因此大多数对新创建对象的引用可以在活动线程的堆栈和寄存器中找到。通常不需要扫描整个堆来查找对新对象的引用(或证明此类引用不存在),尽管有必要跟踪旧对象被修改为指向的(希望是异常的)情况。新的。
“短暂的”(或“世代”)垃圾收集器试图利用这些观察结果:通过集中精力频繁地回收新创建的对象,为了回收未引用的内存,不需要为整个堆做更昂贵的GC。在某些环境中,与此类完整GC相关的暂停可能会显着且具有破坏性,并且最小化这些暂停的频率(有时是持续时间)可能是EGC的主要目标(尽管可能存在其他好处,例如参考的局部性增加EGC通常会导致稍长的执行时间(以及稍微高一点的摊销的GC时间),但有时也可以提高整体性能;
大多数EGC策略(包括Clozure CL使用的策略)在逻辑上或物理上将记忆划分为相对年轻的对象(“世代”)和一个或多个旧对象区域的一个或多个区域。作为年轻一代成员在一个或多个GC中幸存下来的物体被提升(或“终身”)到老一代,在那里它们可能存活或者可能不能存活足够长的时间以被提升到下一代并且最终可能变得“老”只有在完整的GC证明没有对它们的实时引用时才能回收的对象。这种过滤过程并不完美 – 可能会发生一定程度的过早使用 – 但通常在实践中效果很好。
值得注意的是,最年轻一代的GC通常非常快(在现代CPU上可能只有几毫秒,具体取决于各种因素),Clozure CL的EGC不是并发的,也不提供实时保证。
Clozure CL的EGC维持着三个短暂的世代; 所有新创建的对象都是作为最年轻一代的成员创建的。每一代都有一个相关的 阈,表示在触发GC之前可以分配的字节数和所有年轻代。这些GC将涉及目标生成和所有年轻的(因此可能导致一些过早的期限); 由于老一代的门槛越来越高,它们的频率降低了,并且大多数短命的物体使它成为老一代,往往不能在那里生存很长时间。
EGC可以 启用 要么 残在程序控制下; 在某些情况下,可能会启用它 待用 (因为完整的GC即将来临。)由于可能很难知道或预测其他线程的行为,因此“活动”和“非活动”状态之间的区别并不十分有意义,尤其是涉及本机线程时。
GC页面回收政策
完整GC完成后,它将尝试确保至少(LISP-HEAP-GC-THRESHOLD)虚拟内存可用; 对象将在此内存块中分配,直到它填满,GC被触发,并且该过程重复进行。
许多程序在完全GC(或在几乎静态状态下长时间运行)后使用的逻辑内存量达到接近停滞的水平,因此在第N个完整GC之后用于消耗的逻辑地址范围很可能几乎或完全相同于第N + 1个完整GC使用的地址范围。
默认情况下(传统上在Clozure CL中),GC的策略是“释放”此地址范围内的页面:建议虚拟内存系统页面包含垃圾,并且不需要交换与之关联的任何物理页面在重用之前输出到磁盘并重新映射(重新)映射逻辑地址范围,以便在下次访问时虚拟内存系统将页面填充为零。此策略旨在减少VM系统的负载并将Clozure CL的工作集保持在最低水平。
对于某些程序(特别是那些速度非常快的程序),默认策略可能不太理想:几乎立即释放需要的页面 – 并且懒散地将它们填充回来 – 导致不必要的开销。(如果只是在下一次GC之前重新开始重新启动工作集的大小,则会出现虚假的经济现象。)在这样的环境中,在GC之间“保留”页面的策略可能会更好。
下面描述的功能使用户可以控制此行为。自适应,反馈介导的方法可能会产生更好的解决方案。
“纯”区域是只读的,从图像文件中分页
SAVE-APPLICATION识别代码矢量和实体符号的名称,并将这些对象复制到它创建的图像文件的“纯”区域。(“纯”区域占ROOM功能报告为“静态”空间的大部分内容。)
加载生成的图像文件时,文件的纯区域现在使用只读访问进行内存映射。根据需要从映像文件中分页代码和纯数据(并且不与其他内存区域竞争全局虚拟内存资源。)
代码向量和实习符号pnames是不可变的:尝试更改此类对象的内容是错误的。以前,该错误会以某种随机的方式表现出来。在新方案中,它将在Lisp内核中表现为“未处理的异常”错误。可能会使内核检测到对只读空间的虚假,意外写入,并在这种情况下发出lisp错误信号,但它还没有这样做。
应该以某种模式打开和/或映射图像文件,该模式不允许从其他进程写入文件的内存映射区域。我不知道该怎么做; 在Clozure CL映射文件时写入文件会产生不可预测和令人不快的结果。SAVE-APPLICATION将删除其输出文件的目录条目并创建一个新文件; 在使用可能覆盖现有图像文件的文件系统实用程序(例如tar)时,可能需要小心。
弱参考
通常,“弱引用”是对对象的引用,该对象不阻止对象被垃圾收集。例如,假设您要保留特定类型的所有对象的列表。如果您不采取特殊步骤,那么您拥有它们列表的事实将意味着对象始终是“实时”的,因为您始终可以通过列表引用它们。因此,它们永远不会被垃圾收集,并且它们的内存永远不会被回收,即使它们在程序中没有被引用。如果您不想要此行为,则需要弱引用。
Clozure CL支持两种对象的弱引用:弱哈希表和种群。
使用标准Common Lisp函数创建弱哈希表,该函数make-hash-table扩展为接受关键字参数:weak。哈希表可能在其键或其值方面较弱。要使用弱键创建哈希表,请make-hash-table使用以下选项调用 :weak t,或等效地:weak:key。要使用弱值,请使用:weak:value。当密钥较弱时,相等性测试必须是#’eq(因为否则没有意义)。
当发生垃圾收集时,如果没有对该对的弱元素(键或值)的非弱引用,则从哈希表中删除键值对。
通常,当您希望使用散列来存储有关您在其中查找的对象的一些额外信息时,弱键散列表非常有用,而当您希望将散列用作索引时,弱值散列表非常有用。查找对象。
填充封装了一个对象,导致该对象的某些引用被认为是弱的。Clozure CL支持两种种群:列表,在这种情况下,封装的对象是一个元素列表,当没有对该元素的非弱引用时,这些元素被拼接出列表; 和alists,在这种情况下,封装的对象是一个conses列表,如果没有对cons的汽车的非弱引用,则会从列表中拼接出来。
如果您正在使用弱引用交互实验,记住,对象是不是死了,如果它是由过去的三交互式评估表达式之一返回,因为变量*,**和***。简单的解决方法是在调用之前评估一些无意义的表达式gc,以便从REPL变量中获取对象。
弱引用词典
make-population &key type initial-contents[功能]
类型
人口类型,:LIST(默认)或:ALIST
初始内容
:alist用于初始化总体的一系列元素(或用于)。序列本身(以及alist的情况下的conses)不存储在总体中,创建新列表或alist来保存元素。
创建指定类型的新填充。
population-type population[功能]
返回population一个:LIST或的类型:ALIST
population-contents population[功能]
返回封装的列表population。请注意,只要存在对此列表的直接(非弱)引用,垃圾收集器就不会对其进行修改。因此,遍历列表是安全的,甚至可以修改它,与任何其他列表没有区别。如果您希望元素再次变为可垃圾回收,则必须直接停止引用列表。
(setf ( population-contents population) contents)[功能]
设置封装在列表population来 contents。 Contents未复制,直接使用。
垃圾收集词典
gc[功能]
导致尽快发生完整的GC。返回NIL。
lisp-heap-gc-threshold[功能]
返回内核变量的值,该变量指定完整GC后要在堆中保留的可用空间量。
set-lisp-heap-gc-threshold new-threshold[功能]
新门槛
请求的新lisp-heap-gc-threshold。
设置内核变量的值,该变量指定在完全GC到新值之后要在堆中保留的可用空间量,该值应该是非负的fixnum。返回该内核变量的值(可能比指定的大一些)。
use-lisp-heap-gc-threshold[功能]
尝试增大或缩小lisp的堆空间,以便可用空间(大约)等于当前堆阈值。返回NIL
egc arg[功能]
ARG
广义布尔值
如果arg为非零,则启用EGC,否则禁用EGC。返回先前启用的状态。虽然这个函数是线程安全的(在某种意义上它是对序列化的调用),但是从多个线程打开和关闭EGC并没有多大意义……
egc-enabled-p[功能]
如果在呼叫时启用EGC则返回T,否则返回NIL。
egc-active-p[功能]
如果EGC在呼叫时处于活动状态,则返回T,否则返回NIL。由于这通常是一个不稳定的信息,因此当涉及本机线程时,此功能是否有用是不明确的。
egc-configuration[功能]
作为多个值返回与最年轻的短暂生成,中间短暂生成和最早的短暂生成相关联的阈值的大小(以KB为单位)
configure-egc generation-0-size generation-1-size generation-2-size[功能]
代-0尺寸
最小代的请求阈值大小,以千字节为单位
代-1-尺寸
请求的中间代阈值大小,以千字节为单位
代-2-尺寸
请求的最老代的阈值大小,以千字节为单位
使指示的阈值大小生效。每个阈值表示在触发GC之前可以在该年份和所有年轻代中分配的总大小。设置值时禁用EGC。(提供的阈值大小在Clozure CL 0.14中向上舍入为64K字节的倍数,在早期版本中向上舍入为32KB的倍数。)
gc-retain-pages arg[功能]
ARG
广义布尔值
试图影响GC以保留/回收在GC之间分配的页面,如果arg为真,则以其他方式释放它们。这通常是分页和其他VM考虑因素之间的权衡。
gc-retaining-pages[功能]
如果GC尝试在完整GC和NIL之间保留页面,如果它试图释放它们以提高VM分页性能,则返回T.
http://mip.i3geek.com