了解代码性能问题,特别是不必要的非确定性问题,一个有用的工具是端口分析器工具,如ECLiPSe和Logtalk。Logtalk ports_profiler
工具是可移植的,所以我们可以在这里使用它。我们从包装您的代码开始(来自您的 gist 链接):
:- use_module(library(lists), []).
:- object(step).
:- public(step_n/3).
:- use_module(lists, [reverse/2]).
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, In, D, 1, 0).
calculate_digit_n_(_, [], D, _, Acc) :- D1 is abs(Acc), divmod(D1, 10, _, D).
calculate_digit_n_(N, [I | Is], D, M, Acc) :-
digit_m(N, M, C), P is C*I, M1 is M+1, Acc1 is Acc+P,
calculate_digit_n_(N, Is, D, M1, Acc1).
phase_step(In, Out) :-
length(In, L), L1 is L + 1, phase_step_(In, Out, L1, 1, []).
phase_step_(_, Out, L, L, Acc) :- reverse(Out, Acc).
phase_step_(In, Out, L, N, Acc) :-
N < L, calculate_digit_n(N, In, D), N1 is N + 1,
phase_step_(In, Out, L, N1, [D | Acc]).
step_n(0, I, I).
step_n(N, In, 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.
接下来(使用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, [1, 2, 3, 4, 5, 6, 7, 8], X).
340 15578 30816 46054 61292 76530 91768 107006 122244 137482
X = [3, 6, 4, 4, 0, 6, 7, 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)并不意味着或要求谓词是确定性的。在您的情况下,当
step_n/3
谓词的规则中的
最后一个调用是对自身的调用时,Prolog编译器可以应用TCO。这意味着可以在
特定的递归调用上保存堆栈帧。这并不意味着在递归调用之前没有创建选择点。使用
once/1
(如您在评论中提到的)只是丢弃了调用
phase_step/2
时创建的选择点,因为该谓词本身是非确定性的。这就是表格所显示的内容。
step_n/3
谓词也是非确定性的,因此在第一个参数为
0
时调用它会创建一个选择点,这发生在您使用零作为第一个参数调用谓词时
或者当查询的证明达到此递归定义的基本情况时。
prolog_current_frame
对于所有递归实例都给出相同的值,那么它就被优化了吗?我检查了一个简单的“double”函数和我的“step_n”,“double”给出了相同的值,而我的“step_n”没有。 - rajashekar