Execution time of Prolog program always increases

Consider the following meaningless Prolog program.

:- dynamic assigned/1.

:- op(681,xfx,user:'~=').

again(0) :- !.
again(N) :- 
	assertz(assigned(msw(tr(s0),3,s0)~=1)), 
	assertz(assigned(msw(lunch(s0),2,s)~=0)), 
	assertz(assigned(chmm([s, p, s],s0,0,3)~=1)),
	check,
	retractall(assigned(_)), N1 is N-1, again(N1).

check :- 
	assigned(msw(tr(s0),3,s0)~=1),
	assigned(msw(lunch(s0),2,s)~=0), 
	assigned(chmm([s, p, s],s0,0,3)~=1).

The execution time of this program always increases:

?- time(again(10000)).
% 90,001 inferences, 2.719 CPU in 2.720 seconds (100% CPU, 33099 Lips)
true.

?- time(again(10000)).
% 90,000 inferences, 24.299 CPU in 24.301 seconds (100% CPU, 3704 Lips)
true.

?- time(again(10000)).
% 90,000 inferences, 62.708 CPU in 62.713 seconds (100% CPU, 1435 Lips)
true.

I understand that the above Prolog program is highly inefficient, but why should the execution time increase each time I call the goal again(10000). Can it be a bug of Prolog?

I also tried adding garbage_collect_clauses like this:

:- dynamic assigned/1.

:- op(681,xfx,user:'~=').

again(0) :- !.
again(N) :- 
	assertz(assigned(msw(tr(s0),3,s0)~=1)), 
	assertz(assigned(msw(lunch(s0),2,s)~=0)), 
	assertz(assigned(chmm([s, p, s],s0,0,3)~=1)),
	check,
	retractall(assigned(_)),garbage_collect_clauses, N1 is N-1, again(N1).

check :- 
	assigned(msw(tr(s0),3,s0)~=1),
	assigned(msw(lunch(s0),2,s)~=0), 
	assigned(chmm([s, p, s],s0,0,3)~=1).

Indeed after adding garbage_collect_clauses, the execution time reduced by almost half; however, the problem still persists. Now I get the following execution time:

?- time(again(10000)).
% 100,002 inferences, 1.797 CPU in 1.798 seconds (100% CPU, 55640 Lips)
true.

?- time(again(10000)).
% 100,000 inferences, 9.297 CPU in 9.298 seconds (100% CPU, 10756 Lips)
true.

?- time(again(10000)).
% 100,000 inferences, 34.824 CPU in 34.825 seconds (100% CPU, 2872 Lips)
true.

Is this a bug of Prolog, or am I doing something wrong? Is there a way to resolve this issue?

Thanks in advance.

Furthermore, some magic happens if I replace assigned(msw(tr(s0),3,s0)~=1) by assigned(msw(tr(s0),3,s0,1)~=1). So now the program is:

:- dynamic assigned/1.

:- op(681,xfx,user:'~=').

again(0) :- !.
again(N) :- 
	assertz(assigned(msw(tr(s0),3,s0,1)~=1)), 
	assertz(assigned(msw(lunch(s0),2,s)~=0)), 
	assertz(assigned(chmm([s, p, s],s0,0,3)~=1)),
	check,
	retractall(assigned(_)), N1 is N-1, again(N1).

check :- 
	assigned(msw(tr(s0),3,s0,1)~=1),
	assigned(msw(lunch(s0),2,s)~=0),
	assigned(chmm([s, p, s],s0,0,3)~=1).

Now the execution time does not always increase, moreover, the execution time reduces drastically,

?- time(again(10000)).
% 90,001 inferences, 0.098 CPU in 0.100 seconds (98% CPU, 914988 Lips)
true.

?- time(again(10000)).
% 90,000 inferences, 0.123 CPU in 0.125 seconds (98% CPU, 732503 Lips)
true.

?- time(again(10000)).
% 90,000 inferences, 0.101 CPU in 0.103 seconds (98% CPU, 889696 Lips)
true.

What is happening here?

If you run ?- jiti_list(assigned). you’ll see it creates a clause index in the first case and not in the new one. I’d have to do more digging to get to the bottom of this. May do some day, but not now …

Current SWI-Prolog dynamic database isn’t designed to act as a highly volatile global variable. I guess the heuristics for choosing when to create indexes and when to run clause garbage collection can be tweaked to make this one go better. You better pass your state in a variable or use a global variable …