On Thursday, 20 March 2014 at 08:51:08 UTC, monarch_dodra wrote:
Agreed.
There is consensus it seems. I will make the fix ;-)
I think there is 0 doubt that reference semantics is the way to
go. An advantage of using class is that it is still *possible*
to place them on the stack with Scoped, or with some future
language mechanic. On the other hand, if we implement as
reference structs, then that's that.
I suppose the one concern I have is whether these reference-type
RNGs might generate unpleasant unintended effects with other
range objects in Phobos. One thing that I really must do now
that the basic design is in place is to systematically go through
all the different ways in which these ranges could interact with
deterministic ranges, and whether there are any issues to address.
Furthermore, even in terms of performance, I think a heap
allocated PRNG will still flat-out beat the value based one, if
only because of the size of the damn thing.
I don't know if you or anyone else has run the simple benchmark
programs I created, but my impression was that for the RNGs and
other functions here there is no significant speed difference
between the std.random2 class implementations and their
std.random struct predecessors. Where there _is_ a difference it
seems more likely to be down to algorithm rather than
class/struct or heap/stack.
For example, my new Mersenne Twister is slightly slower, but
probably because it's carrying extra parameters compared to that
of std.random. On the other hand, generating random numbers by
foreach'ing over uniform() calls does not seem to have any speed
difference with popFrontN()'ing over a Uniform Distribution.
That said, being able to allocate them on the malloc heap, and
not the GC heap, would be (IMO) also a valid design.
A simple and dumb design might be to still implement them with
value semantic but:
1. Disable postblit.
2. Make .save() return a "Random*"
This would mean
1. No dangers of accidental copy.
2. Range* is a ForwardRange.
3. Trivially allows GC/malloc/stack allocation.
With good aliases ("alias Random = RadomImpl*;"), and a "make!"
template we could make the "default useage" transparent to this
mechanism yet make it easy to get our hands under the hood.
One strict objection here: .save returning a Random* would mean
that this kind of unittest will fail, no?
auto rng1 = someRandomGenType;
auto rng2 = rng1.save;
rng1.popFrontN(10);
rng2.popFrontN(10);
assert(rng1.front == rng2.front);
More generally, I think that, while I don't object to doing
complicated stuff behind the scenes to get things simple and easy
for the user, the problem I have with the above is that it really
seems to require so much effort to create something which comes
naturally with the current std.random2 design.
I didn't check the code yet, but a "middle ground" could be to
make all constructors private, and disable T.init. Then, we
force construction through a make! template.
This might not be what's most convenient, but it would allow us
to potentially change the design at a later stage, without
breaking user code.
The idea of making constructors private and forcing the user to
use the convenience functions is a very interesting one. As long
as they provide an adequate interface to completely control all
implementation parameters, it could provide a way to have
significant leeway in controlling exactly how RNG instances are
initialized.
On the other hand it really feels obnoxious to cut users off from
being able to use objects directly :-(
Do you have a simple but very fast function that generates
uniforms in [0.0, 1.0]? :-)
AFAIK, the allocation issue is only for ranges? "uniform" is
just a function, I don't think it affected by the issue. Even
if you are operating on a "passed range", either ranges are
reference semantics, and you take by value, or they are value
semantic, and you take by ref. Either way, you have to pay for
the indirection.
I think the issue here is just that it's possible to implement a
really fast high-quality algorithm for uniformly-distributed
floating point numbers in [0, 1). That has all sorts of uses not
just for Phobos users but also internally in e.g. random
distributions (for example, it'll give a significant speed boost
to NormalDistribution).