Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No setting the seed value after creation? #5

Closed
JG-Adams opened this issue Feb 6, 2023 · 4 comments
Closed

No setting the seed value after creation? #5

JG-Adams opened this issue Feb 6, 2023 · 4 comments

Comments

@JG-Adams
Copy link

JG-Adams commented Feb 6, 2023

I'd like to be able to modify the seed number during runtime. It's a useful utility. Is this RNG good for that?

setSeed(int64_t)
getSeed() return int64_t

@drohmf
Copy link

drohmf commented Mar 11, 2023

To re-seed, you can assign a new instance with the desired seed:

auto rng = Xoshiro::Xoshiro256Plus( 1234 );
//... use rng ...

rng = Xoshiro::Xoshiro256Plus( 6789 );
//... use rng with the new seed ...

It's unlikely that methods like setSeed/getSeed would be added here. Xoshiro generators were designed for high performance contexts, keeping the minimal state required to run the generator. Storing the seed in every generator would likely be seen as an unwelcome overhead.

You can track the seed separately, as shown in the examples; or use a wrapper class to store the seed, something like:

template <class Generator>
struct Seedful : Generator
{
    using seed_type = std::uint64_t;

    constexpr explicit Seedful(seed_type seed = XoshiroCpp::DefaultSeed) noexcept
        : Generator(seed), m_seed(seed)
    {}

    constexpr void setSeed(seed_type seed)
    {
        m_seed = seed;
        this->deserialize(Generator(seed).serialize());
    }

    [[nodiscard]] 
    constexpr seed_type getSeed() const noexcept 
    { 
        return m_seed;
    }

  private:
    seed_type m_seed;
};

Usage:

auto rngA = Seedful<XoshiroCpp::Xoshiro256Plus>( 1234 );
    
assert(rngA.getSeed() == 1234); 
for (int i : {1, 2, 3})  { std::cout << rngA() << "\n"; }
assert(rngA.getSeed() == 1234);

rngA.setSeed( 555 );
assert(rngA.getSeed() == 555); 

auto rngB = XoshiroCpp::Xoshiro256Plus( 555 );
for (int i: {1, 2, 3}) { assert(rngA() == rngB()); }

Full example on godbolt.

@JG-Adams
Copy link
Author

JG-Adams commented Mar 13, 2023

Very nice! :)
I don't really consider reallocating an object just to change the state a desirable thing. Wouldn't that be slower than to have mutable variable member?
I've concluded that this RNG may not be good for something like a world generator because some seeds would turn out ugly.
The use case is just to have randomness in general right? It is very fast for that.

@drohmf
Copy link

drohmf commented Mar 14, 2023

I don't really consider reallocating an object just to change the state a desirable thing. Wouldn't that be slower than to have mutable variable member?

Normally, yes, for expensive objects. However, in this case, assigning a new instance is the efficient way, because:

  1. Re-seeding a generator necessarily means restarting the state of the generator to use the seed from the beginning. That's exactly what the constructors for XoshiroCpp:: generators do.
  2. XohiroCpp:: generators are small (64bit, 128bit, or 256bit). That's the size of 1 to 4 pointers (on 64-bit platforms).
  3. You'll notice that all XoshiroCpp:: methods and constructors are constexpr. So if the inputs (i.e. seeds) are known at compile time, then all these constructions (e.g. Xoshiro::Xoshiro256Plus(6789)) and member function calls (e.g. .serialize()) will be done at compile time. Otherwise, if the inputs are dynamic, these methods and constructors will almost always get inlined by the optimizer without any "function call overhead" or "object instantiation overhead", effectively leaving you with the equivalent of directly mutating a member variable.

The use case is just to have randomness in general right? It is very fast for that.

Yes, it strives to generate a uniform random distribution of unsigned numbers within the full range of bits. So a 64-bit generator would produce numbers in the range [0, 2^64).

The intended usage would be similar to how you would use std::mt19937_64, for example. And you would typically want to feed those uniformly generated values to a distribution of your liking (normal, poisson, uniform, etc.).

... some seeds would turn out ugly

This might be a little off topic for this thread, but you might find more "visually pleasing" randomness from something like https://github.com/Reputeless/PerlinNoise.

@JG-Adams
Copy link
Author

Very interesting! Thanks for taking the time to teach me that! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants