我正在进行一些实验,以比较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
在某些C
和R
中意外发生故障。在故障驱动的循环中,这基本上不会被注意到,而上述构造将失败。
一些系统为此提供了谓词forall/2
。
时间
如果你进行计时,最好只使用列表的第一个元素并计算差异:
statistics(walltime, [T0|_]),
Goal,
statistics(walltime, [T1|_]),
D is T1 - T0.
通过这种方式,目标的替代答案将给你一个更有意义的价值。