prologsicstus-prologmeta-predicate

maplist/2 vs extra recursion predicate with SICStus Prolog


Consider the following simple interaction with SICStus Prolog:

$ sicstus -f
SICStus 4.8.0 (x86_64-linux-glibc2.28): Sun Dec  4 13:17:41 UTC 2022
[...]
| ?- use_module(library(between)),
     use_module(library(lists)).
[...]
| ?- compile(user).
% compiling user...
| f(X) :- integer(X).
|
| fs([]).
| fs([F|Fs]) :- f(F), fs(Fs).
|
| maplist_f(Xs) :- maplist(f,Xs).
| 
% compiled user in module user, 64 msec 698032 bytes
yes

I was expecting that fs/1 and maplist_f/1 have pretty much the same performance—thanks to the use of "logical loops." What I got with a simple test, however, is this:

| ?- statistics(runtime,_),
     (numlist(1000,Xs),repeat(100000),fs(Xs),false;true),
     statistics(runtime,[_,RT]).
RT = 284 ? 
yes
| ?- statistics(runtime,_),
     (numlist(1000,Xs),repeat(100000),maplist_f(Xs),false;true),
     statistics(runtime,[_,RT]).
RT = 2758 ? 
yes

The variant using maplist/2 is ~10X slower: what's going on?!

The SICStus Prolog profiler puts the blame on meta-calls—but why are they even used here?


Solution

  • As false mentioned in a comment, the logical loops do not do anything clever with the meta argument. Instead the effect of maplist_f(Xs) is pretty much equivalent to:

    :- meta_predicate(mfs(+, 1)).
    mfs([], _G1).
    mfs([F|Fs], G1) :- call(G1, F), mfs(Fs, G1).
    
    mfs_f(Fs) :- mfs(Fs, f).
    

    And, unsurprisingly, passing an extra meta argument (f) and calling it with call/2, has a significant cost compared to the direct call done by fs/1.

    In fact, what may be more surprising, calling maplist_f(Xs) (which uses logical loops internally) is slightly slower than calling mfs_f(Fs) (which uses an ordinary predicate, with first argument indexing).