I meant relative to rdet, which is implemented as a wrapper of a predicate.
I was always hoping that there could exist some kind of directive – at the caller – that would suspend in the VM the creation of choice points, essentially a built-in once/1 applied across a block of code, / each of its calls. – to increase performance – i.e. to even eliminate the need for a cut.
I’ve just eliminated all my uses of rdet/1, replacing it with det/1. Performance is possibly a bit worse (within “epsilon”) but conflated with making changes from :- to =>, so I can’t say anything definitive.
UPDATE: I did a quick edit of my code, removing det/1 declarations and changing most => to :-!, … the result was about 2% faster (which is close to “epsilon”). The biggest difference seems to be from removing the det/1 declarations. I don’t have a comparison with the rdet/1 wrappers, but the appear to have been about the same as the det/1 declarations; in other words, the over head of det/1 is roughly the same as the overhead of once/1.
But take this with a grain of salt … I’ve seen +/- 5% variation in performance when I look at some older compilation runs.
(Also, I have the ‘optimise’ flag turned on.)
Bottom line: I don’t think that det/1 and => have a significant effect on performance one way or the other; they should be used as development tools, not for performance.
(BTW, I was pleasantly surprised that my code was well-behaved and didn’t leave choicepoints hanging around, despite having very few cuts.)
My plan is to use det/rdet as a development tool and condition them away for production code – i hope that this is a sound thing to do.
I also have many must_have checks that are then conditioned away …
But, ultimately, it seems that a certain core – once its stable – I will have to rewrite in C/C++ although, i am now strongly considering Rust (in the hope that integration into Prolog will be reasonably simple and performant to accomplish)
I was pleasantly surprised that my code that used :- was well-behaved and didn’t leave choicepoints. AFAICT, => doesn’t prevent the creation of choice points within the goals on the r.h.s. of the => – it merely commits to the first head+guards that matches.
(I had written some code to extend pack(rdet) to check for choicepoints, but probably won’t deploy it (I haven’t got around to writing unit tests) because det/1, /0, /1 seem to cover most of my needs … if anyone is interested, my extensions to rdet are in rdet2.pl.
Yes, i noticed that earlier and found it confusing – given that => was billed as overcoming the lack of explicit determinism - it still requires programmer’s discipline, while creating a kind of asymmetry: non-determinisic guards are treated as if they are wrapped in a once/1 – whereas the right hand side, can have non-determinism with choice points left and backtracking.
I thought this diminishes the conceptual simplicity of =>, by leaving determinism in the body in hands of developers again.
All that is left is then a commitment to a clause (and, a more constrained, single sided, unification – which is a contribution to steadyfastness).
That is (Goal->true), no? Using library(apply_macros), once is expanded this way. The git version now also has $(Goal) which is a runtime validation of determinism.
They should not have. In my tests using det/1 has barely measurable effect on performance. There is one very trivial instruction in the supervisor of the predicate. => has a bit more impact as it guarantees a choicepoint when the head unification is started, to remove this again if a matching clause was found. It is technically possible to make SSU faster than normal unification and avoid the need for this choicepoint, making => clauses faster. Most likely this will happen at some point in time. There are several ways out though and I haven’t made up my mind on what to try first. The most trivial is adding dedicated SSU unification instructions, but this is more work and in my experience adding instructions tend to make the system faster on micro benchmarks but often slower on real programs.
One trick I’m considering is to fake that the trail stack is full when doing SSU unification. That would allow a check at only one place and avoid the need for a choicepoint for doing SSU unification.
det/1 is surely for development. => is supposed to be for runtime. You surely do not want to switch it on and off.
Everything that is needed to rewrite a loaded program without touching the source is there.
But, within the VM, I imagined (naively) that each_of_once doen’t prune, but disables the mechanism for choice points creation – since each goal is declared as once, all the way to the end of the each_of_once/1 declaration.
Edit:
A lot of my code that asserts structures is of this kind, and backtrackng is not needed – instead failed guards and calls need to raise exceptions.
All the additions to better support “functional” code are first of all meant to make it easier to write and debug such code. The impact on performance is small and rather than spending time to exploit some of the additional knowledge to improve performance it is probably more fruitful to target speedup of the VM in general.
P.s. Did anyone ever try to compile SWI-Prolog using the Intel C compiler? So far I have used MSVC, GCC and CLANG. GCC wins overall. Still, on the Pereira microbenchmark, set the other compilers do some of the benchmarks considerably better (as in up to 50%)
I think that capturing intended functional steps and determinism with many once/1, as i did now, in the code had an effect on clarity and debug-ability – it clutters the code and expansions make debugging cluttered with the expanded syntax.
I am now experimenting with removing then all and carefully manage the determinism via cuts at the source and other means, but this actually obscures the functional steps i had in mind in the client code – since it all now looks non-deterministic in the client code.
Dan
The once/1 construct should only be used to if you have a fundamentally nondet predicate and you want the first answer only. E.g. once(sub_atom(Haystack, Where, _, _, Needle)) to find the first match of Needle in Haystack.
It looks like this further emphasizes the need for a construct that makes a goal deterministic at the client side.
once/1 – intends to means, get me a first solution only (knowing that several might exist).
once_det/1 – may intent to mean, treat goal as deterministic – upon backtracking don’t try it again.
Unless, such a constuct goes against the spirit of Prolog, where determinism should be a property of the predicate and not the calling context.
This would solve the problem I mentioned upthread, where %! b64_to_utf8_to_atom(+BytesBase64, -Atom) is det. but I can’t mark it with det/1 because it can fail in a checking context such as b64_to_utf8_to_atom('ZmlsZQ==',package). And I suspect that it would be too complex to have compiler automation or some kind of goal-expansion to work around this by transforming the checking call to b64_to_utf8_to_atom(‘ZmlsZQ==’,MaybePackage),MaybePackage=package.`