Using: SWI-Prolog version 8.4.0
I have two versions of a simple predicate which takes a string Input
and an “cursor” position PosIn
and advances the cursor to any skip white space characters (defined by ws_char/1
) to position PosOut
:
skip_ws0(Input,PosIn,PosOut) :-
sub_atom(Input,PosIn,1,_,C),
ws_char(C), !,
P is PosIn+1,
skip_ws0(Input,P,PosOut).
skip_ws0(_Input,PosIn,PosIn).
skip_ws1(Input,PosIn,PosOut) :-
((sub_atom(Input,PosIn,1,_,C), ws_char(C))
-> P is PosIn+1,
skip_ws1(Input,P,PosOut)
; PosOut = PosIn
).
ws_char(' ').
ws_char('\t').
ws_char('\n').
ws_char('\r').
Timing the difference:
?- S="1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 ",
string_length(S,L),time((between(1,10000,_),between(0,L,I),skip_ws0(S,I,_),fail)).
% 5,530,001 inferences, 0.383 CPU in 0.385 seconds (100% CPU, 14426855 Lips)
false.
?- S="1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 ",
string_length(S,L),time((between(1,10000,_),between(0,L,I),skip_ws1(S,I,_),fail)).
% 6,540,001 inferences, 0.315 CPU in 0.315 seconds (100% CPU, 20751433 Lips)
false.
So skip_ws1
outperforms skip_ws0
by about 25% on this 100 character input string. On a considerably longer test input (over 30,000 characters), the difference is much, much larger (Note the difference in Lips).
?- json_sample1K(S),string_length(S,L),time((between(1,10,_),between(0,L,I),skip_test:skip_ws0(S,I,_),fail)).
% 1,949,421 inferences, 4.509 CPU in 4.510 seconds (100% CPU, 432372 Lips)
false.
?- json_sample1K(S),string_length(S,L),time((between(1,10,_),between(0,L,I),skip_test:skip_ws1(S,I,_),fail)).
% 2,254,701 inferences, 0.109 CPU in 0.109 seconds (100% CPU, 20608945 Lips)
false.
Other observations:
- Although the large string was not tested, the effect is observable on SWISH.
-
profile/1
port count data is identical for the two versions on the large input string. - looking at the
vm_list
output, pretty much the only differences are a) 1 clause vs. 2, and b)i_cut
inskip_ws0
vs.c_cut
inskip_ws1
.
While it’s obvious which version I’ll be using, I’m curious as to whether there’s any explanation for this effect.