这是否取决于 phase_step 是否只有一个解决方案?
有点,但更强大一点:这取决于phase_step 是确定性,这意味着不留下任何“选择点”。一个选择点是未来要探索的路径;不一定会产生进一步的解决方案,但 Prolog 仍然需要检查。
例如,这是确定性的:
phase_step_det(X, X).
它只有一个解决方案,Prolog 不会提示我们更多:
?- phase_step_det(42, Out).
Out = 42.
以下有一个单一的解决方案,但它不是确定性的:
phase_step_extrafailure(X, X).
phase_step_extrafailure(_X, _Y) :-
false.
看到解决方案后,Prolog 仍有一些需要检查的地方。即使我们可以通过查看代码来判断某些东西(第二个子句)会失败:
?- 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.
为什么 TCO 依赖于 phase_step 谓词?
如果还有更多路径需要探索,那么关于这些路径的数据必须存储在某处。那个“某处”是某种堆栈数据结构,对于每个未来的路径,堆栈上都必须有一个框架。这就是您的内存使用量增加的原因。随之而来的是计算时间(以下使用您的 step_n 的副本以及我上面对应的 phase_step 变体):
?- 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, 42, Out)).
% 400,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, 42, Out)).
% 400,000 inferences, 4.231 CPU in 4.231 seconds (100% CPU, 94546 Lips)
Out = 42 ;
% 4 inferences, 0.007 CPU in 0.007 seconds (100% CPU, 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
探索这一点的一种方法是使用 SWI-Prolog 调试器,它可以向您展示备选方案(= 选择点 = 未来要探索的路径):
?- trace, step_n_det(5, 42, Out).
Call: (9) step_n_det(5, 42, _1496) ? skip % I typed 's' here.
Exit: (9) step_n_det(5, 42, 42) ? alternatives % I typed 'A' here.
[14] step_n_det(0, 42, 42)
Exit: (9) step_n_det(5, 42, 42) ? no debug % I typed 'n' here.
Out = 42 ;
false.
?- trace, step_n_extrafailure(5, 42, Out).
Call: (9) step_n_extrafailure(5, 42, _1500) ? skip
Exit: (9) step_n_extrafailure(5, 42, 42) ? alternatives
[14] step_n_extrafailure(0, 42, 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, 42) ? no debug
Out = 42 ;
false.
所有这些选择都对应于额外的解释器框架。如果您使用 SWI-Prolog 的可视化调试器,它还会向您显示堆栈的图形表示,包括所有打开的选择点(尽管我一直觉得这很难理解)。
因此,如果您想要 TCO 并且不增加堆栈,您需要确定性地执行阶段步骤。您可以通过使phase_step 谓词本身具有确定性来做到这一点。您也可以在 step_n 内部的 phase_step 调用之后进行剪切。
以下是来自上方的呼叫,每个 phase_step 后面都有一个剪辑:
?- time(step_n_det(100_000, 42, Out)).
% 400,001 inferences, 0.017 CPU in 0.017 seconds (100% CPU, 24204529 Lips)
Out = 42 ;
% 7 inferences, 0.000 CPU in 0.000 seconds (83% CPU, 737075 Lips)
false.
?- time(step_n_extrafailure(100_000, 42, Out)).
% 400,000 inferences, 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, 42, Out)).
% 400,000 inferences, 0.023 CPU in 0.023 seconds (100% CPU, 17732727 Lips)
Out = 42 ;
% 5 inferences, 0.000 CPU in 0.000 seconds (94% CPU, 219742 Lips)
false.
不要盲目地进行切割,只有在了解您真正需要它们的位置和原因之后。请注意,在extrafailure 的情况下,cut 只删除了失败,但在twosolutions 的情况下,它删除了实际的解决方案。