This is a bit off topic, but now that I read your code carefully enough, I noticed that you are doing something that is very much like this other thing, namely, raising to the power. I once shared the code, it is here:
So, if you wanted to not have to code gen_string_of_spaces/2
yourself, but still keep the same idea, it would go something like this:
:- use_module(power).
gen_string_of_spaces(N, Spaces) :-
power(" ", N, string_concat, "", Spaces).
This is it. We are back to a one-liner, as with using format, and it has roughly the same hackiness index.
I have explained it in the original post, but briefly:
- the first argument is the Element, in this case a string with one space;
- the second argument is the power, here the length of the final string;
- the third argument is the operation, here string_concat/3
- the fourth argument is the neutral element, the empty string (if your N is 0)
- the last argument is the result
I added the following two goals to your test suite:
goal_power(SzMax, Spaces) :-
random_between(0, SzMax, RL),
power(" ", RL, string_concat, "", Spaces).
goal_format(SzMax, Spaces) :-
random_between(0, SzMax, RL),
format(string(Spaces), "~t~*|", [RL]).
… and I also added them to the list of tested predicates:
test("generate strings of spaces of random length and drop them immediately after creation") :-
maplist([Goal]>>create_and_drop(Goal),[goal_special,goal_direct,goal_power,goal_format]).
% SNIP
test("generate strings of spaces of random length and store them in a list") :-
maplist([Goal]>>create_and_collect(Goal),[goal_special,goal_direct,goal_power,goal_format]).
Now, when I run your tests, I see:
% PL-Unit: stringy_performance
% 3,712,138 inferences, 0.526 CPU in 0.526 seconds (100% CPU, 7063825 Lips)
drop them immediately (100000 calls) (100 max size) using goal 'goal_special': ""
% 11,194,928 inferences, 0.900 CPU in 0.901 seconds (100% CPU, 12433048 Lips)
drop them immediately (100000 calls) (100 max size) using goal 'goal_direct': ""
% 4,369,662 inferences, 0.536 CPU in 0.537 seconds (100% CPU, 8146089 Lips)
drop them immediately (100000 calls) (100 max size) using goal 'goal_power': ""
% 700,000 inferences, 0.313 CPU in 0.314 seconds (100% CPU, 2237162 Lips)
drop them immediately (100000 calls) (100 max size) using goal 'goal_format': ""
.
% 3,707,469 inferences, 0.649 CPU in 0.650 seconds (100% CPU, 5715648 Lips)
collect in list (100000 calls) (100 max size) using goal 'goal_special': ""
% 11,202,787 inferences, 1.316 CPU in 1.318 seconds (100% CPU, 8510957 Lips)
collect in list (100000 calls) (100 max size) using goal 'goal_direct': ""
% 4,367,630 inferences, 0.573 CPU in 0.574 seconds (100% CPU, 7623688 Lips)
collect in list (100000 calls) (100 max size) using goal 'goal_power': ""
% 700,001 inferences, 0.307 CPU in 0.308 seconds (100% CPU, 2278379 Lips)
collect in list (100000 calls) (100 max size) using goal 'goal_format': ""
. done
% All 2 tests passed
true.
The results are pretty similar between runs.
Please confirm my interpretation: it seems that using format(string(Spaces), "~t~*|", [N])
is still roughly twice as fast as the other approaches, with short strings? I suspect that doing less iteration but generating longer strings might make your initial implementation (and the “power”, which is basically the same) somewhat faster. I tried it with:
callcount(10000).
max_string_size(1000).
Indeed, now I get:
% PL-Unit: stringy_performance
% 606,014 inferences, 0.095 CPU in 0.095 seconds (100% CPU, 6353769 Lips)
drop them immediately (10000 calls) (1000 max size) using goal 'goal_special': ""
% 10,019,866 inferences, 0.766 CPU in 0.767 seconds (100% CPU, 13079676 Lips)
drop them immediately (10000 calls) (1000 max size) using goal 'goal_direct': ""
% 647,857 inferences, 0.092 CPU in 0.092 seconds (100% CPU, 7050067 Lips)
drop them immediately (10000 calls) (1000 max size) using goal 'goal_power': ""
% 70,000 inferences, 0.153 CPU in 0.154 seconds (100% CPU, 456227 Lips)
drop them immediately (10000 calls) (1000 max size) using goal 'goal_format': ""
.
% 607,793 inferences, 0.122 CPU in 0.122 seconds (100% CPU, 4992397 Lips)
collect in list (10000 calls) (1000 max size) using goal 'goal_special': ""
% 10,156,645 inferences, 1.353 CPU in 1.354 seconds (100% CPU, 7506170 Lips)
collect in list (10000 calls) (1000 max size) using goal 'goal_direct': ""
% 649,262 inferences, 0.104 CPU in 0.105 seconds (100% CPU, 6213561 Lips)
collect in list (10000 calls) (1000 max size) using goal 'goal_power': ""
% 70,001 inferences, 0.155 CPU in 0.156 seconds (100% CPU, 450529 Lips)
collect in list (10000 calls) (1000 max size) using goal 'goal_format': ""
. done
% All 2 tests passed
true.
To be honest, I find this at least a bit surprising. I would have expected that using format is going to be always faster. I suspect that string_concat/3 is doing some trickery; I would have to read the implementation if I really wanted to know