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?
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:And, unsurprisingly, passing an extra meta argument (
f) and calling it withcall/2, has a significant cost compared to the direct call done byfs/1.In fact, what may be more surprising, calling
maplist_f(Xs)(which uses logical loops internally) is slightly slower than callingmfs_f(Fs)(which uses an ordinary predicate, with first argument indexing).