reverse/2 uses a variable named Bound
, to prevent going off to infinity - as a performance penalty on each iteration.
However, since the arguments in reverse/2 can be swapped, we can instead use the more usual version, and check whether to swap the arguments to remain safe:
reverse_faster(Lst, Rev) :-
nonvar_first_preferably(Lst, Rev, L, R),
reverse_faster_(L, [], R).
reverse_faster_([], Ys, Ys).
reverse_faster_([X|Xs], Rs, Ys) :-
reverse_faster_(Xs, [X|Rs], Ys).
nonvar_first_preferably(L1, L2, F1, F2) :-
( nonvar(L1)
-> (F1, F2) = (L1, L2)
; nonvar(L2)
-> (F1, F2) = (L2, L1)
% Preferring not to change the order, if no benefit
; (F1, F2) = (L1, L2)
).
This is still safe, and has a surprisingly significant performance improvement:
?- numlist(1, 12_000_000, L), garbage_collect, time(reverse(L, R)).
% 12,000,002 inferences, 1.227 CPU in 1.229 seconds (100% CPU, 9775973 Lips)
% Slightly longer, with arguments swapped
?- numlist(1, 12_000_000, L), garbage_collect, time(reverse(R, L)).
% 12,000,002 inferences, 1.400 CPU in 1.401 seconds (100% CPU, 8572785 Lips)
vs
?- numlist(1, 12_000_000, L), garbage_collect, time(reverse_faster(L, R)).
% 12,000,004 inferences, 0.703 CPU in 0.703 seconds (100% CPU, 17078104 Lips)
% Arguments get swapped back, so no performance penalty
?- numlist(1, 12_000_000, L), garbage_collect, time(reverse_faster(R, L)).
% 12,000,004 inferences, 0.704 CPU in 0.704 seconds (100% CPU, 17055462 Lips)
Presumably this is a candidate to replace the built-in reverse/2?