当全局启用发生检查时,Prolog 是否需要 GC?

Does Prolog need GC when the occurs check is globally enabled?

据我所知,sound unification,SLD 解析不应该创建循环数据结构(这是正确的吗?)

如果是这样,理论上,可以以不需要垃圾收集 (GC) 的方式实现 Prolog。但话又说回来,一个人可能不会。

具体来说:

:- set_prolog_flag(occurs_check, true).
:- set_prolog_flag(gc, false). /* is this safe? */

好吧,某事 必须释放多重引用的内存,其中存在可以在计算的任何步骤中删除的引用。这种情况与结构是否循环无关。

考虑变量 AB 在内存中命名相同的结构(它们“命名相同的术语”)。该结构是从 2 个地方引用的。假设定义 B 的谓词成功或失败。 Prolog 处理器不能只释放该结构:它仍然从 A 引用。这意味着您至少需要引用计数来确保您不会过早释放内存。那是一个引用计数垃圾收集器。

我不知道在任何特定的 Prolog 实现中实现了什么样的垃圾收集(有很多方法,一些比其他更适合 Prolog ......在一个并非完全无关的上下文中 25 年 Java 已创建 all of these) 但您需要使用一个,不一定是引用计数。


(循环结构只对垃圾收集有特殊意义,因为引用计数垃圾收集算法无法释放循环结构,因为循环中的所有单元格的引用计数至少为 1。)

(另外,恕我直言,永远不要相信您必须自己调用 free 的编程语言。可能有 Greenspun's tenth rule 的变体(“任何足够复杂的 C 或 Fortran 程序包含一个临时的、非正式指定的、错误缠身的、缓慢的一半 Common Lisp 的实现。") 因为任何用编程语言编写的程序都必须调用 free 你自己包含一个临时的、非正式指定的、漏洞百出的、缓慢的垃圾收集算法实现。”)

(OTOH,Rust 似乎采取了一种 middle way,将一些工作卸载给开发人员,但具有在变量超出范围时能够决定是否释放内存的优势。但是 Rust不是序言。)

在 Prolog (同样值得注意的是,并非所有 Prolog 系统都提供对循环项的全面支持,但其中大多数都支持某种形式的垃圾收集。

例如,假设您的代码中有以下从数字到原子的调用序列:

...,
number_codes(Number, Codes),
atom_codes(Atom, Codes),
...

这里,Codes 是一个临时 列表,应该进行垃圾回收。另一个例子,假设您正在调用 setof/3 来获得一个有序的结果列表,您只对前两个感兴趣:

...,
setof(C, x(X), [X1, X2| _]),
...

您刚刚创建了另一个临时列表。或者您忘记了 sub_atom/5 并决定使用 atom_concat/3 来检查原子的前缀:

...,
atom_concat(Prefix, _, Atom),
...

第二个参数,您不关心的原子后缀(因此是匿名变量),是您刚刚创建的 临时 原子。但并非所有 Prolog 系统都提供原子垃圾收集器,这可能会导致长 运行 应用程序出现问题。

但是即使您认为自己已经仔细编写了代码以避免创建临时项,Prolog 系统仍然可能在 运行 您的代码时创建垃圾。 Prolog 系统将不同的内存区域用于不同的目的,并且操作可能需要在不同的内存区域之间制作内存段的临时副本,具体取决于实现。 Prolog 系统可以用一种语言编写,例如Java,这可能最终会处理那些垃圾。但很可能它是用 C 或 C++ 编写的,并且在内部使用了某种垃圾收集。更不用说 Prolog 系统可能会占用一大块内存来证明查询,然后在查询终止后回收该内存。

这里是安全的:

:- set_prolog_flag(gc, false).

但是,如果您的 Prolog 系统有垃圾收集功能,将其关闭可能不是一个好主意,因为即使发生检查设置为 true,临时结果中仍可能存在垃圾。不断删除垃圾可以改善代码的局部性,即您的内存因缓存未命中而变得更少:

p(X,Y) :- q(X,Z), r(Z,Y).

变量 Z 可能指向一些临时需要的 Prolog 项。大多数现代 Prolog 系统都可以通过所谓的环境修整来删除此类 Prolog 项。

但是发生检查为一种特殊类型的垃圾收集开辟了道路。也就是说,由于不能再出现循环项,因此可以使用引用计数。一个具有引用计数的旧 Prolog 系统是这里的野兽:

xpProlog:高性能扩展纯 Prolog - Lüdemann,1988
https://open.library.ubc.ca/media/download/pdf/831/1.0051961/1

此外,Jekejeke Prolog 仍然进行引用计数。引用计数的一个问题是属性变量,它可以创建一个循环项,例如下面的 freeze/2 通过冻结目标创建一个循环回到变量:

?- freeze(X, (write(X), nl)).

编辑 2021 年 9 月 4 日:
还可能需要垃圾收集的是 setarg/3。它
可以创建无法通过
轻易删除的循环 引用计数。

?- X = f(0), setarg(1,X,X).
X = f(X).

由于 setarg/3 是可回溯的,循环将在
中消失 回溯,至少我猜是这样。但是周期可能还是会打扰
当我们深入回溯并且 运行 内存不足时。

或者循环可能不会通过回溯消失,因为我们
使用非回溯版本 nb_setarg/3.