Prolog,测试标签启发法



我正在进行一些实验,以比较Sictus Prolog中的不同标记启发式方法。

但我总是陷入"资源错误:内存不足"。

我很确定我在测试代码中做错了什么。

以下代码将复制我的问题:

:- use_module(library(clpfd)).
:- use_module(library(lists)).
atest( R, C ):-
X is R * C,
length( M, X),
domain( M, 0, X),
all_distinct( M ),
statistics(walltime, [_,_SinceLast]),
labeling( [],M ),
statistics(walltime, [_,SinceLast]),
write('Labeling time: '), write(SinceLast),write('ms'),nl.

% Testcode for looping through alot of variations and run one test for each variant
t1:-
Cm = 1000,
Cr = 1000,
(
for(C,0, Cm),
param([Cm,Cr])
do
(
for(R, 0, Cr ),
param([C])
do
atest( C, R )
)
).      

在调用t1谓词后不久,我得到一个"资源错误:内存不足"异常。

我想我应该在打电话给atest释放资源后做点什么?

另外:这是衡量贴标签时间的正确方法吗?有更好的方法吗?

您没有做任何严格错误的事情,但您正在尝试运行

length(Xs,N), domain(Xs,0,N), all_distinct(Xs), labeling([],Xs).

对于N高达1000000。该系统构造了一个深度为N的搜索树,并且必须存储每个级别的变量和约束系统的中间状态。对于大N,这需要大量内存,并且对于使用大N的单个运行,很可能已经出现内存溢出。

第二个问题是,您正在递归循环中运行基准测试,即您正在有效地创建连接

atest(0,0), ..., atest(1000,1000)

由于每次调用atest/2都能成功地获得第一个解决方案,并保持其搜索状态,这意味着您正在尝试创建一个级别为250500250000的搜索树。。。

最简单的改进是在第一个解决方案之后,通过将代码更改为once(atest(C,R))来减少每次搜索。进一步的改进是在故障驱动的循环中运行基准

(
between(0,Cm,C),
between(0,Cr,R), 
once(atest(C,R)),
fail
;
true
)

这将更快更快地释放内存,并减少由于垃圾收集而导致的测量失真。

顶层隐藏备选答案

如果你在SICStus的顶级shell上用一些较小的值测试t1,你可能会错误地认为t1只有一个答案/解决方案。然而,事实并非如此!所以顶层对你隐藏了其他答案。这是SICStus顶层中的一种特殊行为,如果查询不包含变量,则不会显示进一步的答案。但是总共有x!为您的标签提供许多解决方案,x,而其他解决方案的时间是一些随机值。内存不足是因为对于每个测试用例,Prolog都会保留一个记录,以便继续为每个测试用例生成下一个解决方案。

循环

我不建议使用故障驱动的环路进行测试,而是使用以下环路,它非常相似但更安全:

+ (
between(0, Cm, C),
between(0, Cr, R),
+ atest(C, R)
).

与故障驱动环路的最大区别在于,atest/2在某些CR中意外发生故障。在故障驱动的循环中,这基本上不会被注意到,而上述构造将失败。

一些系统为此提供了谓词forall/2

时间

如果你进行计时,最好只使用列表的第一个元素并计算差异:

statistics(walltime, [T0|_]),
Goal,
statistics(walltime, [T1|_]),
D is T1 - T0.

通过这种方式,目标的替代答案将给你一个更有意义的价值。

最新更新