1st draft of complete class-based std.random successor

Joseph Rushton Wakeling joseph.wakeling at webdrake.net
Thu Mar 20 14:59:38 PDT 2014


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).


More information about the Digitalmars-d-announce mailing list