天真地假设被认为是有害的:带有累加器的Prolog谓词会破坏(全局)堆栈,但天真版本不会

天真地假设被认为是有害的:带有累加器的Prolog谓词会破坏(全局)堆栈,但天真版本不会,prolog,tail-recursion,prolog-toplevel,Prolog,Tail Recursion,Prolog Toplevel,我尝试了几个版本的简单谓词,它从额外的逻辑世界中提取随机值,并将它们放入列表中。我假设带有累加器的版本是,因为递归调用后不会发生任何事情,所以存在一条优化路径,但它不是(它使用“全局堆栈”)。另一方面,“天真版本”显然已经优化成一个循环。这是SWI序言 为什么累加器版本不受尾部调用优化的影响 以下是谓词版本 最慢,耗尽本地堆栈空间(预期) 在这里,我们只允许一个带有函数符号的头部使事情变得明确 % Slowest, and uses 4 inferences per call (+ 1 at t

我尝试了几个版本的简单谓词,它从额外的逻辑世界中提取随机值,并将它们放入列表中。我假设带有累加器的版本是,因为递归调用后不会发生任何事情,所以存在一条优化路径,但它不是(它使用“全局堆栈”)。另一方面,“天真版本”显然已经优化成一个循环。这是SWI序言

为什么累加器版本不受尾部调用优化的影响

以下是谓词版本

最慢,耗尽本地堆栈空间(预期) 在这里,我们只允许一个带有函数符号的头部使事情变得明确

% Slowest, and uses 4 inferences per call (+ 1 at the end of recursion). 
% Uses "local stack" indicated in the "Stack limit (1.0Gb) exceeded" 
% error at "Stack depth: 10,321,204":
% "Stack sizes: local: 1.0Gb, global: 7Kb, trail: 1Kb"

oracle_rands_explicit(Out,Size) :- 
   Size>0, !, 
   NewSize is Size-1, 
   oracle_rands_explicit(R,NewSize), 
   X is random_float, 
   Out = [X-Size|R].
oracle_rands_explicit([],0).
更快,并且不会耗尽堆栈空间 同样,我们只允许一个没有函数符号的head将事情显式化,但是我们将递归调用移到body的末尾,这显然会产生不同

% Same number of inferences as Slowest, i.e. 4 inferences per call
% (+ 1 at the end of recursion), but at HALF the time.
% Does not run out of stack space! Conclusion: this is tail-call-optimized.

oracle_rands_explicit_last_call(Out,Size) :- 
   Size>0, !, 
   NewSize is Size-1,    
   X is random_float, 
   Out = [X-Size|R],
   oracle_rands_explicit_last_call(R,NewSize).
oracle_rands_explicit_last_call([],0).
紧凑,推理更少,并且不会耗尽堆栈空间 在这里,我们允许函数符号在头部更紧凑的符号。还是幼稚的递归

%每个调用只有3个推论(+1在递归结束时),但大约 %与“更快”相同的时间。 %不会耗尽堆栈空间!结论:这是尾部呼叫优化

oracle_rands_compact([X-Size|R],Size) :- 
   Size>0, !, 
   NewSize is Size-1,    
   X is random_float, 
   oracle_rands_compact(R,NewSize).
oracle_rands_compact([],0).
基于累加器且意外耗尽(全局)堆栈空间 增编:“紧凑”版本的另一个版本。 在这里,我们将
Size
参数移动到第一个位置,并且不使用
。但是只有更多的条款才可能引起注意

oracle_rands_compact2(Size,[X-Size|R]) :- 
   Size>0, 
   NewSize is Size-1,    
   X is random_float, 
   oracle_rands_compact2(NewSize,R).
oracle_rands_compact2(0,[]).
尝试使用
L
代替匿名变量,并在调用后使用
L

X = 10000000, time(oracle_rands_compact2(X,L)),L=[].
% 30,000,002 inferences, 6.129 CPU in 6.159 seconds (100% CPU, 4894674 Lips)

X = 10000000, time(oracle_rands_compact(L,X)),L=[].
% 30,000,001 inferences, 5.865 CPU in 5.892 seconds (100% CPU, 5115153 Lips)
也许快一点。上面的数字有点不同,一个人真的需要生成100次左右的完整统计数据

重新引入切割是否会加快速度(似乎不会减慢速度)


真的不能说。

这一切都取决于顶级shell和对
\u
的实际解释。试一试

 ?- X = 50000000, time(oracle_rands_compact(L,X)),L=[].
相反,它将或多或少与累加器版本一样糟糕,累加器版本必须首先生成整个列表,然后才将其交给
reverse/2
。看看这个用途

?- set_prolog_flag(trace_gc, true).
true.

?- X = 50000000, time(oracle_rands_compact(_,X)).
% GC: gained 0+0 in 0.001 sec; used 440+8; free 126,520+129,008
% GC: gained 0+0 in 0.000 sec; used 464+16; free 126,496+129,000
% GC: gained 0+0 in 0.000 sec; used 464+16; free 126,496+129,000
...

?- X = 50000000, time(oracle_rands_compact(L,X)),L=[].
% SHIFT: l:g:t = 0:1:0 ...l+g+t = 131072+262144+131072 (0.000 sec)
% GC: gained 0+0 in 0.002 sec; used 123,024+16; free 135,008+129,000
% SHIFT: l:g:t = 0:1:0 ...l+g+t = 131072+524288+131072 (0.000 sec)
% GC: gained 0+0 in 0.003 sec; used 257,976+24; free 262,200+128,992
% SHIFT: l:g:t = 0:0:1 ...l+g+t = 131072+524288+262144 (0.000 sec)
% SHIFT: l:g:t = 0:1:0 ...l+g+t = 131072+1048576+262144 (0.000 sec)
% GC: gained 0+0 in 0.007 sec; used 520,104+16; free 524,360+260,072
...

如果我们做到了,您的
\u compact
版本可以通过交换参数和删除剪切来加速。经典的第一参数索引能够处理这种情况,避免任何选择点。(上次我检查时,SWI有WAM风格的第一个参数索引和一个用于多个参数的较低版本)

一个甚至不需要在调用后使用
L=[]
。只需将
\uu
更改为
L
即可导致压缩版本在同一点上耗尽堆栈空间。SWI,显然是受YAP的启发。Prolog在编写上是紧凑的,但紧凑性表明在调用堆栈上正在对海量数据结构进行大量的工作。
L=[]
避免打印术语,因为它相当大。有了更好的GC,列表
L
可以减少到
[收集的|收集的]
。关于
\u compact*
:在整个列表中比较它们是没有意义的。相反,只与原始查询进行比较。否则GC占主导地位。
?- oracle_rands_acc(X,4).
X = [0.7768407880604368-4, 0.03425412654687081-3, 0.6392634169514991-2, 0.8340458397587001-1].

?- X = 1000000, time(oracle_rands_acc(_,X)).
% 4,000,004 inferences, 0.798 CPU in 0.810 seconds (99% CPU, 5009599 Lips)

?- X = 50000000, time(oracle_rands_acc(_,X)).
ERROR: Stack limit (1.0Gb) exceeded
ERROR:   Stack sizes: local: 1Kb, global: 0.9Gb, trail: 40.6Mb
ERROR:   Stack depth: 12,779,585, last-call: 100%, Choice points: 6
ERROR:   In:
ERROR:     [12,779,585] user:oracle_rands_acc(37220431, [length:12,779,569], _876)
oracle_rands_compact2(Size,[X-Size|R]) :- 
   Size>0, 
   NewSize is Size-1,    
   X is random_float, 
   oracle_rands_compact2(NewSize,R).
oracle_rands_compact2(0,[]).
X = 10000000, time(oracle_rands_compact2(X,L)),L=[].
% 30,000,002 inferences, 6.129 CPU in 6.159 seconds (100% CPU, 4894674 Lips)

X = 10000000, time(oracle_rands_compact(L,X)),L=[].
% 30,000,001 inferences, 5.865 CPU in 5.892 seconds (100% CPU, 5115153 Lips)
oracle_rands_compact3(Size,[X-Size|R]) :- 
   Size>0, !,
   NewSize is Size-1,    
   X is random_float, 
   oracle_rands_compact3(NewSize,R).
oracle_rands_compact3(0,[]).
?- X = 10000000, time(oracle_rands_compact3(X,L)),L=[].
% 30,000,001 inferences, 5.026 CPU in 5.061 seconds (99% CPU, 5969441 Lips)
 ?- X = 50000000, time(oracle_rands_compact(L,X)),L=[].
?- set_prolog_flag(trace_gc, true).
true.

?- X = 50000000, time(oracle_rands_compact(_,X)).
% GC: gained 0+0 in 0.001 sec; used 440+8; free 126,520+129,008
% GC: gained 0+0 in 0.000 sec; used 464+16; free 126,496+129,000
% GC: gained 0+0 in 0.000 sec; used 464+16; free 126,496+129,000
...

?- X = 50000000, time(oracle_rands_compact(L,X)),L=[].
% SHIFT: l:g:t = 0:1:0 ...l+g+t = 131072+262144+131072 (0.000 sec)
% GC: gained 0+0 in 0.002 sec; used 123,024+16; free 135,008+129,000
% SHIFT: l:g:t = 0:1:0 ...l+g+t = 131072+524288+131072 (0.000 sec)
% GC: gained 0+0 in 0.003 sec; used 257,976+24; free 262,200+128,992
% SHIFT: l:g:t = 0:0:1 ...l+g+t = 131072+524288+262144 (0.000 sec)
% SHIFT: l:g:t = 0:1:0 ...l+g+t = 131072+1048576+262144 (0.000 sec)
% GC: gained 0+0 in 0.007 sec; used 520,104+16; free 524,360+260,072
...