With some small optimizations (adding this to the PGO training data and use a fast path for small integers (GNU-Prolog only has small integers)), we get
101 ?- t(99, 100000).
% 100,101 inferences, 0.189 CPU in 0.189 seconds (100% CPU, 530888 Lips)
true.
102 ?- t(101, 100000).
% 100,101 inferences, 0.356 CPU in 0.356 seconds (100% CPU, 281294 Lips)
true.
vs GNU-Prolog
| ?- t_(99, 100000).
(136 ms) yes
| ?- t_(101, 100000).
(137 ms) yes
Note that the cycle check is at depth 100 rather than 1,000 as I claimed before. So, without the cycle test SWI-Prolog is less than 50% slower than GNU-Prolog. Now, is the cycle test a bad idea? Hard to say. Such deeply nested evaluable expressions are rare. Not having a check will eventually raise a memory resource error, but only after eating all the memory it can get from the machine. Eating all memory of the machine can cause all sorts of issues. Probably we can get this a bit faster. There is not much point though as evaluation of expressions not known at compile time is rare.
For the fun, a changed version of the test below, where t2/2
compiles the expression. Now for
t(1000, 100000)
we get 3.795 sec and for t2(1000, 100000)
we get 1.041 sec. For the compiled version the compiler does the cycle check, finds the function (ar_add()) from an array and gets the constant 1
more efficiently.
t(D,N) :-
add(D, Expr),
time(t_(N, Expr)).
t_(N, Expr) :-
( between(1, N, _),
_ is Expr,
fail
; true
).
:- dynamic t2_/1.
t2(D, N) :-
add(D, Expr),
retractall(t2_(_)),
asserta((t2_(N) :-
( between(1, N, _),
_ is Expr,
fail
; true
))),
time(t2_(N)).
add(0, 1) :- !.
add(N, (Expr+N)) :-
N2 is N - 1,
add(N2, Expr).