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

define Random.GLOBAL_RNG = TaskLocalRNG() #51388

Merged
merged 1 commit into from
Sep 29, 2023
Merged

Conversation

rfourquet
Copy link
Member

GLOBAL_RNG is a relic from pre-v1.3 when our global RNG was not thread-safe:

const GLOBAL_RNG = MersenneTwister()

GLOBAL_RNG was never really specified, but was referred to in docstrings (of rand etc.), and people had to use it in packages in order to provide a default RNG to their functions. So we have to keep defining Random.GLOBAL_RNG for the time being.

In v1.3, we didn't have anymore a unique global RNG, but instead one per thread; in order to keep code using GLOBAL_RNG working, we got this new definition as a clever hack:

struct _GLOBAL_RNG <: AbstractRNG end
const GLOBAL_RNG = _GLOBAL_RNG()

I.e. GLOBAL_RNG is just a handle, which refers to the actual threaded-RNG when passed to rand functions. This entails defining most function taking a default_rng() to also accept _GLOBAL_RNG. See #41123, and #41235 which is not even merged.

But since v1.7, we have Random.default_rng() (our now official way to refer to the default RNG) returning TaskLocalRNG(), which is itself a "handle" (singleton). So we can as well redefine GLOBAL_RNG to be TaskLocalRNG() instead of _GLOBAL_RNG(), with the advantage that all the relevant methods are already defined for TaskLocalRNG.

The only expected difference in behavior is seed!(GLOBAL_RNG, seed), which previously would update Random.GLOBAL_SEED (a hack used by @testset), but now is equivalent to seed!(TaskLocalRNG(), seed), which doesn't update this global seed. This precise behavior was never documented (GLOBAL_SEED is purely internal), so this should be fine.

@rfourquet rfourquet changed the title make Random.GLOBAL_RNG == TaskLocalRNG() define Random.GLOBAL_RNG = TaskLocalRNG() Sep 19, 2023
@rfourquet rfourquet added the domain:randomness Random number generation and the Random stdlib label Sep 19, 2023
# But the global RNG is now TaskLocalRNG() and doesn't store its seed; in order to not break `@testset`, we now
# store the seed used in a call like `seed!(seed)` *without* an explicit RNG in `GLOBAL_SEED`; the wording of the
# feature above was sufficiently unprecise (e.g. what exactly is the "global RNG"?) that this solution seems fine;
# in a breaking julia version, we would probably remove GLOBAL_SEED and this `@testset` feature
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really? I think this feature is really useful. I actually was just recently looking into ways to add more things to be reset at the start and end of each @testset (think additional RNGs and other global state). Unfortunately the only way I could see was to create my own AbstractTestSet subtype and then change every single @testset invocation to explicitly specify that type, which is rather a burden :-(

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that's the first time i see positive feedback on this feature, it has usually been "meh" ! So this comment about removing this was more of a reflexion of what I thought was the general sentiment; and also the fact that since we don't use MersenneTwister as the global RNG, there is a mismatch between what we say:

Before the execution of the body of a @testset, there is an implicit call to Random.seed!(seed) where seed is the current seed of the global RNG.

and what we can do, because the "global RNG" doesn't have a seed (there are multiple global RNGs, one per task, and we currently store only one seed.

But anyway, i'm very glad to see someone else finding this feature useful :) I will remove this comment, and i have a plan to make the feature more robust.

`GLOBAL_RNG` is a relic from pre-v1.3 when our global RNG was not thread-safe:
```
const GLOBAL_RNG = MersenneTwister()
```

`GLOBAL_RNG` was never really specified, but was referred to in docstrings (of `rand` etc.),
and people had to use it in packages in order to provide a default RNG to their functions.
So we have to keep defining `Random.GLOBAL_RNG` for the time being.

In v1.3, we didn't have anymore a unique global RNG, but instead one per thread; in order
to keep code using `GLOBAL_RNG` working, we got this new definition as a clever hack:
```
struct _GLOBAL_RNG <: AbstractRNG end
const GLOBAL_RNG = _GLOBAL_RNG()
```
I.e. `GLOBAL_RNG` is just a handle, which refers to the actual threaded-RNG when passed to
`rand` functions. This entails defining most functions taking a `default_rng()` to also
accept `_GLOBAL_RNG`. See #41123, and #41235 which is not even merged.

But since v1.7, we have `Random.default_rng()` (our now official way to refer to the default
RNG) returning `TaskLocalRNG()`, which is itself a "handle" (singleton). So we can as well
redefine `GLOBAL_RNG` to be `TaskLocalRNG()` instead of `_GLOBAL_RNG()`, with the advantage
that all the relevant methods are already defined for `TaskLocalRNG`.

The only expected difference in behavior is `seed!(GLOBAL_RNG, seed)`, which previously would
update `Random.GLOBAL_SEED` (a hack used by `@testset`), but now is equivalent to
`seed!(TaskLocalRNG(), seed)`, which doesn't update this global seed. This precise
behavior was never documented (`GLOBAL_SEED` is purely internal), so this should be fine.
@rfourquet rfourquet merged commit b74daf5 into master Sep 29, 2023
6 checks passed
@rfourquet rfourquet deleted the rf/yeet-global-rng branch September 29, 2023 08:31
vchuravy pushed a commit to JuliaLang/Test.jl that referenced this pull request Oct 7, 2023
`GLOBAL_RNG` is a relic from pre-v1.3 when our global RNG was not
thread-safe:
```
const GLOBAL_RNG = MersenneTwister()
```

`GLOBAL_RNG` was never really specified, but was referred to in
docstrings (of `rand` etc.), and people had to use it in packages in
order to provide a default RNG to their functions. So we have to keep
defining `Random.GLOBAL_RNG` for the time being.

In v1.3, we didn't have anymore a unique global RNG, but instead one per
thread; in order to keep code using `GLOBAL_RNG` working, we got this
new definition as a clever hack:
```
struct _GLOBAL_RNG <: AbstractRNG end
const GLOBAL_RNG = _GLOBAL_RNG()
```
I.e. `GLOBAL_RNG` is just a "handle", which refers to the actual
threaded-RNG when passed to `rand` functions. This entails defining most
functions taking a `default_rng()` to also accept `_GLOBAL_RNG`. See
JuliaLang/julia#41123, and JuliaLang/julia#41235 which is not even merged.

But since v1.7, we have `Random.default_rng()` (our now official way to
refer to the default RNG) returning `TaskLocalRNG()`, which is itself a
"handle" (singleton). So we can as well redefine `GLOBAL_RNG` to be
`TaskLocalRNG()` instead of `_GLOBAL_RNG()`, with the advantage that all
the relevant methods are already defined for `TaskLocalRNG`.

The only expected difference in behavior is `seed!(GLOBAL_RNG, seed)`,
which previously would update `Random.GLOBAL_SEED` (a hack used by
`@testset`), but now is equivalent to `seed!(TaskLocalRNG(), seed)`,
which doesn't update this global seed. This precise behavior was never
documented (`GLOBAL_SEED` is purely internal), so this should be fine.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
domain:randomness Random number generation and the Random stdlib
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants