August 26, 2013
On 26/08/13 17:26, monarch_dodra wrote:
> One more thing. One of the ways I see it (specifically for LF): We can add
> auto-seed later if we decide it was a bad idea to not have it. The opposite
> isn't possible.

Yes.  I feel similar about the question of whether or not pseudo-random RNGs should have .save methods or not.  I can see use-cases, but better to avoid at first (while as you have suggested implementing .dup) and then, if it turns out that was a bad idea, add them.

> Another option I had thought of, was to make the value-types Payloads as public,
> with *massive* "do not use signs". *We* then provide simple and generic (and
> pure/nothrow) GC-based wrappers.

Related to this, I was thinking about how my RandomGenerator implements the isUniformRandom property (it aliases to the corresponding property of the underlying random engine/payload).

Now, if you suppose that the payloads _don't_ define isUniformRandom but define, say, isRandomPayload or isRandomEngine, and then in the RandomGenerator wrapper you have,

    alias isUniformRandom = Payload.isRandomPayload;

... then the isUniformRNG check of other functions WON'T WORK with the raw Payloads.  So you have a static safety check in place that will plain stop users doing stupid stuff.

That is beneficial, because it means that a user can for example make use of the Payload definition to define a new RNG of existing type but with different configuration (e.g. Lagged Fib with different parameter values).

>  From there, if, for whatever reason, a user needs malloc allocation, or heck,
> static allocation, he still has an "underhand" access to the payload
> implementation, but lifecycle management remains *his* responsibility.
>
> I think: Simple and safe by default, with the possibility for extension.

Yes, if we handle isUniformRandom like this.

> Again, for now, I think this is implementation detail. That said, I don't buy
> much into the typo argument. In particular, this is something that gets copy
> pasted only once, so we should be relatively safe.
>
> One of the arguments in favor of "internal" mixins is that of extension: For
> example, laggedFib has a very real reason to implement popFrontN, as it can
> potentially pop thousands of elements at once in o(1). "Internal" mixin makes it
> easy to extend a PRNG's capabilities past that of the "lowest common
> denominator". With a "alias Random = RandomGenerator!RandomPayload", you are
> really limited to whatever RandomGenerator wrapped.

Fair enough, but I think there are other ways of handling that, e.g. via opDispatch.  We ought to be able to make sure that _all_ public methods and properties of the payload get forwarded.

And -- devil's advocate -- how many different extensions are there likely to be in practice?  Is it not the case that we can anticipate them and make sure they are in the wrapper?  How many operations can one reasonably expect from a RNG?

> Honestly, it might just be the simplest thing to do. For one, it would elegantly
> solve the "must be seeded" issue (allocation is initialization). It *guarantees*
> Reference semantics. Finally, the (supposed) overhead should be inexistant
> compared to the compelxity of a PRNG.
>
> The option of allowing "public Payload" definitions could still leave an open
> door for those that need PRNG's, but don't use the GC (think vidjagames).

I would quite like to have Andrei and Walter's input on the struct vs. class aspect, and Phobos design style.

One thought about "public payload" and wrappers: it ought to be possible to design things so that a RandomGenerator wrapper can be initialized by passing a pointer-to-payload instead of a seed, which obviates the need for internal allocation.  And we can use that approach for rndGen() so that the default RNG instance doesn't require GC.
August 26, 2013
On 26/08/13 17:26, monarch_dodra wrote:
> One more thing. One of the ways I see it (specifically for LF): We can add
> auto-seed later if we decide it was a bad idea to not have it. The opposite
> isn't possible.

One note on seeding.  As it stands, some RNGs do have a valid default configuration _without_ seed() being called -- see e.g. Xorshift.
August 27, 2013
On Monday, 26 August 2013 at 21:20:25 UTC, Joseph Rushton
Wakeling wrote:
> On 26/08/13 17:26, monarch_dodra wrote:
>> One more thing. One of the ways I see it (specifically for LF): We can add
>> auto-seed later if we decide it was a bad idea to not have it. The opposite
>> isn't possible.
>
> One note on seeding.  As it stands, some RNGs do have a valid default configuration _without_ seed() being called -- see e.g. Xorshift.

What bothers me about these is that they have a fixed seed value,
which (IMO) is even worst than default seeding to
"unpredicatableSeed"

C's "rand" also "defaults seed". I've "tutored" C++ on codeguru
for years, and years in and years out, I'd get asked "why does my
random program keep creating the same output". This behavior
surprises people.

A PRNG should either error-out on no-seed, or be run-time
unpredictably seeded. The middle ground just gives you the worst
of both...
August 27, 2013
On Monday, 26 August 2013 at 09:47:50 UTC, monarch_dodra wrote:
> FYI, I tweaked my algo a bit, as the benches revealed an issue. Please wait for a new commit before testing my code.

I tweaked the algo a bit. It now runs faster when the amount of bits is the same as the size_t. I also renewed my benches, and did a linux dmd and gdc run.

The good news is the windows ulong|64 bit version is faster than before. The *great* news is that on linux, integral generation is *fast*. It's the fastest PRNG of all, faster than XOS and LC. *AND* it's generating 64 bit ulongs (!). In terms of performance, One could say it *creams* the competition :D

The BAD news though, is that it would appear the floating point generation on linux is *bad*. As in, so bad, it's faster to just generate integrals and divide. Arguably, I think the "native" double generation might be of slightly higher quality, but I don't think it is worth the cost.

Here are the new benches!!!

//=============================================
DMD32 2.063.2 on W764

Integrals
Time for LinearCongruentialEngine!(uint, 16807, 0, 214...):  8168
Time for MersenneTwisterEngine!(uint, 32, 624, 397, 31...): 14185
Time for LaggedFibonacciEngine!(uint, 32u, 607, 273)      :  7079
Time for LaggedFibonacciEngine!(ulong, 48u, 607, 273)     : 11869
Time for LaggedFibonacciEngine!(ulong, 64u, 607, 273)     :  9677

Floating
Time for LinearCongruentialEngine!(uint, 16807, 0, 214...): 12151
Time for MersenneTwisterEngine!(uint, 32, 624, 397, 31...): 19497
Time for XorshiftEngine!(uint, 32, 13, 17, 5)             : 10476
Time for LaggedFibonacciEngine!(uint, 32u, 607, 273)      : 11117
Time for LaggedFibonacciEngine!(ulong, 48u, 607, 273)     : 17415
Time for LaggedFibonacciEngine!(ulong, 64u, 607, 273)     : 15209
Time for LaggedFibonacciEngine!(double, 32u, 607, 273)    : 14882
Time for LaggedFibonacciEngine!(double, 48u, 607, 273)    : 14874
Time for LaggedFibonacciEngine!(real, 64u, 607, 273)      : 19138

//=============================================
Kubuntu 13.04-64 DMD64 HEAD

Integrals
Time for LinearCongruentialEngine!(uint, 16807, 0, 2147...):  6942
Time for MersenneTwisterEngine!(uint, 32, 624, 397, 31,...): 13352
Time for XorshiftEngine!(uint, 32, 13, 17, 15)             :  6267
Time for LaggedFibonacciEngine!(uint, 32LU, 607, 273)      :  4888
Time for LaggedFibonacciEngine!(ulong, 48LU, 607, 273)     : 10401
Time for LaggedFibonacciEngine!(ulong, 64LU, 607, 273)     :  6401

Floating
Time for LinearCongruentialEngine!(uint, 16807, 0, 2147...):  8200
Time for MersenneTwisterEngine!(uint, 32, 624, 397, 31, ..): 15357
Time for XorshiftEngine!(uint, 32, 13, 17, 15)             :  7851
Time for LaggedFibonacciEngine!(uint, 32LU, 607, 273)      :  5399
Time for LaggedFibonacciEngine!(ulong, 48LU, 607, 273)     : 11030
Time for LaggedFibonacciEngine!(ulong, 64LU, 607, 273)     :  6927
Time for LaggedFibonacciEngine!(double, 32LU, 607, 273)    : 20508
Time for LaggedFibonacciEngine!(double, 48LU, 607, 273)    : 20832
Time for LaggedFibonacciEngine!(real, 64LU, 607, 273)      : 34674


//=============================================
Kubuntu 13.04-64 gdc unknown version

Integrals
Time for LinearCongruentialEngine!(uint,16807,0,2147483...):  8397
Time for MersenneTwisterEngine!(uint,32,624,397,31,-172...): 13976
Time for XorshiftEngine!(uint,32,13,17,5)                  :  6135
Time for LaggedFibonacciEngine!(uint,32,607,273)           :  5165
Time for LaggedFibonacciEngine!(ulong,48,607,273)          :  8582
Time for LaggedFibonacciEngine!(ulong,64,607,273)          :  5250

Floating
Time for LinearCongruentialEngine!(uint,16807,0,2147483...):  8499
Time for MersenneTwisterEngine!(uint,32,624,397,31,-172...): 14523
Time for XorshiftEngine!(uint,32,13,17,5)                  :  6194
Time for LaggedFibonacciEngine!(uint,32,607,273)           :  5523
Time for LaggedFibonacciEngine!(ulong,48,607,273)          :  9239
Time for LaggedFibonacciEngine!(ulong,64,607,273)          :  5498
Time for LaggedFibonacciEngine!(double,32,607,273)         : 16733
Time for LaggedFibonacciEngine!(double,48,607,273)         : 16704
Time for LaggedFibonacciEngine!(real,64,607,273)           : 29949
August 28, 2013
On 27/08/13 20:41, monarch_dodra wrote:
> The BAD news though, is that it would appear the floating point generation on
> linux is *bad*. As in, so bad, it's faster to just generate integrals and
> divide. Arguably, I think the "native" double generation might be of slightly
> higher quality, but I don't think it is worth the cost.

"Bad" is a relative term.  It might be slower, but you might (I don't know) be getting something that has better statistical quality.  So, what you might prefer to use depends on your use-case.
August 28, 2013
On 25/08/13 18:58, Joseph Rushton Wakeling wrote:
>    (2) Should we provide a generic payload wrapper, or require RNG creators
>        to implement everything manually?  I strongly support a generic wrapper,
>        as there is too great a risk of implementation error if we require
>        the wrapping-of-a-reference to be done manually each time.

A few issues I ran into when trying to create such a wrapper:

    * Function properties are not necessarily (easily) consistent between
      different RNGs.  For example, it makes sense that popFront() should
      be @safe pure nothrow, but in the Linear Congruential generator
      there are casts which mean that it can't be @safe.

      Now, we _could_ go with the lowest common denominator, but that's not
      a very nice state of affairs.  Is there any way for a wrapper to
      effectively inherit the properties of the functions it's wrapping?

    * @disable this() doesn't seem appropriate for all RNGs, e.g. Xorshift.
      In fact, for _all_ RNGs you probably want to have a default
      initialization.  So, in this case, classes rather than structs start
      to look appealing.
August 28, 2013
On Wednesday, 28 August 2013 at 17:57:13 UTC, Joseph Rushton Wakeling wrote:
> On 25/08/13 18:58, Joseph Rushton Wakeling wrote:
>>   (2) Should we provide a generic payload wrapper, or require RNG creators
>>       to implement everything manually?  I strongly support a generic wrapper,
>>       as there is too great a risk of implementation error if we require
>>       the wrapping-of-a-reference to be done manually each time.
>
> A few issues I ran into when trying to create such a wrapper:
>
>     * Function properties are not necessarily (easily) consistent between
>       different RNGs.  For example, it makes sense that popFront() should
>       be @safe pure nothrow, but in the Linear Congruential generator
>       there are casts which mean that it can't be @safe.
>
>       Now, we _could_ go with the lowest common denominator, but that's not
>       a very nice state of affairs.  Is there any way for a wrapper to
>       effectively inherit the properties of the functions it's wrapping?
>
>     * @disable this() doesn't seem appropriate for all RNGs, e.g. Xorshift.
>       In fact, for _all_ RNGs you probably want to have a default
>       initialization.  So, in this case, classes rather than structs start
>       to look appealing.

I agree that classes over structs is appealing, but I still think that having "semi-Public payload implementations" is important. There are a lot of devs out there that don't use the GC, in particular video games. It would be nice for them to not have to re-write the wheel.

With that said, even with classes, we'd need an easy way to create the classes from the payloads.
August 29, 2013
On Wednesday, 28 August 2013 at 18:43:53 UTC, monarch_dodra wrote:
> With that said, even with classes, we'd need an easy way to create the classes from the payloads.

Don't understand. With classes you don't need payloads. Just converting existing RNG structs to final classes is sufficient.
August 29, 2013
On Thursday, 29 August 2013 at 00:04:08 UTC, Joseph Rushton Wakeling wrote:
> On Wednesday, 28 August 2013 at 18:43:53 UTC, monarch_dodra wrote:
>> With that said, even with classes, we'd need an easy way to create the classes from the payloads.
>
> Don't understand. With classes you don't need payloads. Just converting existing RNG structs to final classes is sufficient.

Yes, that'd be the idea, but I mean that if you do that, you close the door to giving straight access to a struct-implementation.

Also, placing a value-type payload in a class is pretty trivial.
August 29, 2013
On 29/08/13 08:45, monarch_dodra wrote:
> Yes, that'd be the idea, but I mean that if you do that, you close the door to
> giving straight access to a struct-implementation.

Understood, but I'm not sure that's as big an issue as you think.  I exchanged some private emails with Manu and with Adam Ruppe, on how RNGs-as-final-classes might play with games or embedded programming, and I think the consensus was that it wasn't a big deal, so long as all the allocation happened up front.

Adam suggested that e.g. one could avoid GC by allocating memory and using emplace(), so as long as the class is compatible with that approach, it's fine.