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

Reply via email to