I made the following little program to determine if the memory used for goals like freeze(X,Goal)
is reclaimed when X
becomes unreachable:
%:- use_module(library(freeze)). % Ciao Prolog needs this
freeze_many([],[]).
freeze_many([_|Xs],[V|Vs]) :-
freeze(V,throw(error(uninstantiation_error(V),big_freeze_test/3))),
freeze_many(Xs,Vs).
big_freeze_test(N0,N,Zs0) :-
( N0 > N
-> true
; freeze_many(Zs0,Zs1),
N1 is N0+1,
big_freeze_test(N1,N,Zs1)
).
Let's run the following query...
?- statistics, length(Zs,1000), big_freeze_test(1,500,Zs), statistics.
... with different Prolog processors and look at memory consumption. What a difference!
(AMD64) SICStus Prolog 4.3.2 : global stack in use = 108 MB (AMD64) B-Prolog 8.1 : stack+heap in use = 145 MB (i386) Ciao Prolog 1.14.2: global stack in use = 36 MB (~72 MB w/AMD64) (AMD64) SWI-Prolog 7.3.1 : global stack in use = 0.5 MB (AMD64) YAProlog 6.2.2 : global stack in use = 16 MB
When running more iterations with ?- length(Zs,1000), big_freeze_test(1,10000,Zs).
, I made the following observations:
Ciao Prolog reports
{ERROR: Memory allocation failed [in Realloc()]}
before aborting.sicstus-prolog and b-prolog allocate more and more until the machine freezes.
- swi-prolog performs all iterations in 3.554 seconds.
- yap also performs all iterations, but takes 36.910 seconds.
Any ideas why it works with SWI-Prolog and YAProlog, but not with the other ones?
Considering runtime, how come SWI-Prolog beats YAProlog by more than an order of magnitude?
My intuition is leaning towards the interaction of "attributed variables" with "garbage collection". SWI-Prolog and YAProlog have (share?) a different attributed variable API and implementation than the other Prolog processors ... and, then again, it could be something completely different. Thank you!
freeze/2
is neither typical nor exemplary, but rather a desparate measure used as a last resort: a dirty hack which in some cases makes the implementation of the frozen goal a lot easier... it should probably better have no influence on the internal implementation or the design of the suspension mechanism. – Tokenismfreeze/2
is really the goto of constraints. Too crude to be used reliably. – Rotgutgarbage_collect
before thestatistics
. SWI-Prolog is quite aggressive in calling GC while other systems tend to prefer expanding the stacks. Stack expansion is often faster, but uses more memory which is particularly painful if you have many threads. Scalable threading is a focus point for SWI-Prolog. – Liscomb