问题描述
step_n(0,I,I).
step_n(N,In,Out) :-
N > 0,plus(N1,1,N),phase_step(In,T),step_n(N1,T,Out).
phase_step
是一种转换数据的功能。
此step_n
是否会在与phase_step
相同的内存中运行?如果没有,我应该如何重写呢?这将取决于phase_step有一个解决方案吗?
编辑:使用prolog_current_frame
进行调试后,我发现如果phase_step
是一个像Out is In + 1
这样的简单函数,则会进行优化,但不会在my use case中发生。
为什么TCO依赖于phase_step
谓词?
解决方法
这是否取决于phase_step有一个解决方案?
种类很多,但仍然更强:这取决于Reassign Partitions
是否具有确定性,这意味着不会留下任何“选择点”。选择点是未来的探索之路。不一定会产生进一步的解决方案,但Prolog仍然需要检查。
例如,这是确定性的:
Preferred Replica Election
它只有一个解决方案,并且Prolog不会提示我们提供更多信息:
phase_step
以下内容只有一个解决方案,但不是确定性的:
phase_step_det(X,X).
查看解决方案后,Prolog仍需要检查。即使我们通过查看代码可以知道某些东西(第二个子句)将失败:
?- phase_step_det(42,Out).
Out = 42.
以下解决方案不止一种,因此不是确定性的:
phase_step_extrafailure(X,X).
phase_step_extrafailure(_X,_Y) :-
false.
为什么TCO依赖phase_step谓词?
如果要探索其他路径,那么必须在某处存储有关这些路径的数据。 “某处”是某种堆栈数据结构,并且对于每个将来的路径,堆栈上都必须有一个框架。这就是为什么您的内存使用量会增加。随之而来的是计算时间(以下使用您的?- phase_step_extrafailure(42,Out).
Out = 42 ;
false.
的副本以及上面相应的phase_step_twosolutions(X,X).
phase_step_twosolutions(X,Y) :-
plus(X,1,Y).
?- phase_step_twosolutions(42,Out).
Out = 42 ;
Out = 43.
变体):
step_n
探索此问题的一种方法是使用SWI-Prolog调试器,该调试器可以向您显示替代方法(=选择点=要探索的未来路径):
phase_step
所有这些替代方法都对应于额外的解释器框架。如果您使用SWI-Prolog的可视调试器,它还将向您显示堆栈的图形表示,包括所有开放选择点(尽管我总是觉得很难理解)。
因此,如果您想要TCO而不增加堆栈,则需要执行阶段步骤以确定性地执行。您可以通过使?- time(step_n_det(100_000,42,Out)).
% 400,002 inferences,0.017 CPU in 0.017 seconds (100% CPU,24008702 Lips)
Out = 42 ;
% 7 inferences,0.000 CPU in 0.000 seconds (87% CPU,260059 Lips)
false.
?- time(step_n_extrafailure(100_000,000 inferences,4.288 CPU in 4.288 seconds (100% CPU,93282 Lips)
Out = 42 ;
% 100,005 inferences,0.007 CPU in 0.007 seconds (100% CPU,13932371 Lips)
false.
?- time(step_n_twosolutions(100_000,4.231 CPU in 4.231 seconds (100% CPU,94546 Lips)
Out = 42 ;
% 4 inferences,548 Lips)
Out = 43 ;
% 8 inferences,0.005 CPU in 0.005 seconds (100% CPU,1612 Lips)
Out = 43 ;
% 4 inferences,0.008 CPU in 0.008 seconds (100% CPU,489 Lips)
Out = 44 ;
% 12 inferences,0.003 CPU in 0.003 seconds (100% CPU,4396 Lips)
Out = 43 ;
% 4 inferences,0.009 CPU in 0.009 seconds (100% CPU,451 Lips)
Out = 44 . % many further solutions
谓词本身具有确定性来做到这一点。您还可以在?- trace,step_n_det(5,Out).
Call: (9) step_n_det(5,_1496) ? skip % I typed 's' here.
Exit: (9) step_n_det(5,42) ? alternatives % I typed 'A' here.
[14] step_n_det(0,42)
Exit: (9) step_n_det(5,42) ? no debug % I typed 'n' here.
Out = 42 ;
false.
?- trace,step_n_extrafailure(5,Out).
Call: (9) step_n_extrafailure(5,_1500) ? skip
Exit: (9) step_n_extrafailure(5,42) ? alternatives
[14] step_n_extrafailure(0,42)
[14] phase_step_extrafailure(42,42)
[13] phase_step_extrafailure(42,42)
[12] phase_step_extrafailure(42,42)
[11] phase_step_extrafailure(42,42)
[10] phase_step_extrafailure(42,42)
Exit: (9) step_n_extrafailure(5,42) ? no debug
Out = 42 ;
false.
内的phase_step
通话之后进行剪切。
以下是每个phase_step
之后从上方打来的电话:
step_n
仅当您了解真正需要它们的位置和原因后,才可以盲目放置切口。请注意,在phase_step
情况下,切口仅能消除故障,而在?- time(step_n_det(100_000,001 inferences,24204529 Lips)
Out = 42 ;
% 7 inferences,0.000 CPU in 0.000 seconds (83% CPU,737075 Lips)
false.
?- time(step_n_extrafailure(100_000,0.023 CPU in 0.023 seconds (100% CPU,17573422 Lips)
Out = 42 ;
% 5 inferences,0.000 CPU in 0.000 seconds (93% CPU,220760 Lips)
false.
?- time(step_n_twosolutions(100_000,17732727 Lips)
Out = 42 ;
% 5 inferences,0.000 CPU in 0.000 seconds (94% CPU,219742 Lips)
false.
情况下,切口能去除实际的解决方案。
一个用于了解代码性能问题(尤其是不需要的不确定性)的有用工具是 ports profiler 工具,例如ECLiPSe和Logtalk。 Logtalk ports_profiler
工具是可移植的,因此我们可以在这里使用它。我们首先包装您的代码(从您的要点链接开始):
:- use_module(library(lists),[]).
:- object(step).
:- public(step_n/3).
:- use_module(lists,[reverse/2]).
% pattern for the nth digit mth-coeffcient
digit_m(N,M,D) :-
divmod(M,N,Q,_),divmod(Q,4,_,C),(C = 0,D = 0; C = 1,D = 1; C = 2,D = 0; C = 3,D = -1).
calculate_digit_n(N,In,D) :-
calculate_digit_n_(N,D,0).
calculate_digit_n_(_,[],Acc) :- D1 is abs(Acc),divmod(D1,10,D).
calculate_digit_n_(N,[I | Is],Acc) :-
digit_m(N,P is C*I,M1 is M+1,Acc1 is Acc+P,calculate_digit_n_(N,Is,M1,Acc1).
phase_step(In,Out) :-
length(In,L),L1 is L + 1,phase_step_(In,Out,L1,[]).
phase_step_(_,L,Acc) :- reverse(Out,Acc).
phase_step_(In,Acc) :-
N < L,calculate_digit_n(N,D),N1 is N + 1,N1,[D | Acc]).
step_n(0,I,I).
step_n(N,Out) :-
prolog_current_frame(Fr),format('~w ',Fr),N > 0,N1 is N - 1,phase_step(In,T),step_n(N1,T,Out).
:- end_object.
%:- step_n(10,[1,2,3,5,6,7,8],X).
然后(使用SWI-Prolog作为后端,因为这是您告诉我们您正在使用的Prolog系统):
$ swilgt
...
?- {ports_profiler(loader)}.
% [ /Users/pmoura/logtalk/tools/ports_profiler/ports_profiler.lgt loaded ]
% [ /Users/pmoura/logtalk/tools/ports_profiler/loader.lgt loaded ]
% (0 warnings)
true.
?- logtalk_load(step,[debug(on),source_data(on)]).
% [ /Users/pmoura/step.pl loaded ]
% (0 warnings)
true.
?- step::step_n(10,X).
340 15578 30816 46054 61292 76530 91768 107006 122244 137482
X = [3,8] .
?- ports_profiler::data.
------------------------------------------------------------------------------
Entity Predicate Fact Rule Call Exit *Exit Fail Redo Error
------------------------------------------------------------------------------
step calculate_digit_n/3 0 80 80 0 80 0 0 0
step calculate_digit_n_/5 0 720 720 0 720 0 0 0
step digit_m/3 0 640 640 40 600 0 0 0
step phase_step/2 0 10 10 0 10 0 0 0
step phase_step_/5 0 90 90 0 90 0 0 0
step step_n/3 1 10 11 0 11 0 0 0
------------------------------------------------------------------------------
true.
*Exit
列用于确定过程框中是否存在不确定性。要获得有关该工具和解释表结果的帮助,请参见https://logtalk.org/manuals/devtools/ports_profiler.html,但只要一眼就可以看出表phase_step/2
和step_n/3
都是不确定的。
更新
请注意,尾部调用优化(TCO)并不意味着或不需要谓词具有确定性。在您的情况下,Prolog编译器可以应用TCO ,因为step_n/3
谓词的规则中的 last 调用是对自身的调用。这意味着可以在 特定的递归调用中保存堆栈框架。这并不意味着递归调用之前没有创建选择点。使用once/1
(如您在评论中所提到的)仅会丢弃调用phase_step/2
时创建的选择点,因为谓词本身是不确定的。这就是表格显示的内容。 step_n/3
谓词也是非确定性的,因此在第一个参数为0
时调用它会创建一个选择点,当您在第一个参数上调用谓词为零时会发生选择点(当查询的证明达到此递归定义的基本情况时)。