Skip to content

Modernize the PRNG used for rand() by using a Configure option - #24105

Open
scottchiefbaker wants to merge 16 commits into
Perl:bleadfrom
scottchiefbaker:modern_rand
Open

Modernize the PRNG used for rand() by using a Configure option#24105
scottchiefbaker wants to merge 16 commits into
Perl:bleadfrom
scottchiefbaker:modern_rand

Conversation

@scottchiefbaker

@scottchiefbaker scottchiefbaker commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

Per past discussion on p5p we know the drand48 we use as a PRNG has limitations. This PR attempts to cleanly upgrade the PRNG used for rand() calls, while leaving the existing drand48 code in place for internal Perl stuff (hash seed, etc). I found randfunc and seedfunc options in Configure that allow us to point at a new PRNG without affecting the existing code.

I created a new prng.h file which is largely self-contained except for a couple of additions to embed.fnc and Configure. I have included two new PRNG implementations: PCG64 (my preference) and xoroshiro128** as options.

Completed items

  • Modern PRNG implementation (PCG64)
  • Detailed instructions for future devs on how to extend and modify
  • Updated unit tests
  • Verify srand() functionality works as expected
  • Verify the new rand() outputs the full 53 bit state capable from a double (drand48 could only do 48 bits)
    • ./perl -I lib -E 'for (1..5) { printf("%064b\n", rand() * 2**64-1); }'

TODO

  • prng.h does not seem to be rebuilt consistently after changes. Do I need to add this new file to build system?
  • Bikeshed on what the best PRNG is in 2026
  • make regen puts the functions prototypes in a weird location "Used in locale.c and perl.c"
  • Add an option to get a random integer? rand64()?

Alternate options

  • We don't do anything. rand() is "good enough"
  • Point users at CPAN. Random::Simple is a drop-in replacement for rand() and srand() already

Comment thread prng.h Outdated
@scottchiefbaker
scottchiefbaker force-pushed the modern_rand branch 3 times, most recently from 40d4a6d to 1b78937 Compare January 26, 2026 23:17
@scottchiefbaker
scottchiefbaker force-pushed the modern_rand branch 3 times, most recently from 9002fb7 to b0c2c62 Compare February 2, 2026 16:49
@scottchiefbaker scottchiefbaker changed the title Modernize the PRNG used for rand() by add a Configure option Modernize the PRNG used for rand() by adding a Configure option Feb 3, 2026
@scottchiefbaker scottchiefbaker changed the title Modernize the PRNG used for rand() by adding a Configure option Modernize the PRNG used for rand() by using a Configure option Feb 3, 2026
Comment thread prng.h Outdated
Comment on lines +67 to +68
const uint64_t word = ((prng.state >> ((prng.state >> 59) + 5)) ^ prng.state) * 12605985483714917081ull;
prng.state = prng.state * 6364136223846793005ull + prng.inc;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are these fairly arbitrary numbers, or did they get created by some specific process? Either way, I'd like to see some sort of comment explaining why these numbers in particular.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That algorithm was designed by people WAY smarter than me. Per a previous iteration of this PR I added notes in prng.h with links to various documents talking about PCG64 and how to choose a good PRNG:

// Notes:
// https://zephyrtronium.github.io/articles/randomness.html
// https://docs.oracle.com/en/java/javase/21/core/choosing-prng-algorithm.html
// https://github.com/alvoskov/SmokeRand

TLDR; PCG64 has been tested and vetted by people smarter than me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it could use a reference, maybe https://en.wikipedia.org/wiki/Permuted_congruential_generator

I looked for more "original" references, but the original paper doesn't have the constants that I saw.

Comment thread prng.h Outdated
// Splitmix64 is defined in util.c
static U64 splitmix64(U64 *state);

typedef struct { uint64_t state; uint64_t inc; } pcg64_random_t;

@Leont Leont Mar 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I suspect this should be U64 instead of uint64_t

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think it could be more consistent, some places use U64, some uint64_t

We do use uint64_t in a few other places.

@leonerd leonerd added the defer-next-dev This PR should not be merged yet, but await the next development cycle label Mar 30, 2026
@leonerd

leonerd commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

As we're now in the pre-release change freeze ahead of 5.44.0, this is no longer a suitable candidate for merge. But that means we can spend some time getting it nicely lined up ready for the 5.45.x development series.

@scottchiefbaker

Copy link
Copy Markdown
Contributor Author

I am be interested in helping get this landed, but I lack the requisite core Perl knowledge to get it done.

I'm asking for someone more knowledgeable about the Perl internals to adopt this and get it committed. I will be happy to help test and offer implementation advice.

@tonycoz

tonycoz commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

PR #24294 adds threading support, amongst other changes, but hasn't received any comments.

@scottchiefbaker
scottchiefbaker force-pushed the modern_rand branch 2 times, most recently from b786199 to 2b7518b Compare April 2, 2026 01:45
@scottchiefbaker

Copy link
Copy Markdown
Contributor Author

I pulled in the changes from @tonycoz's branch and made a couple of code clean-ups. Overall it looks good and does what we need it to.

This branch should be mergeable whenever we open things up for v5.45.

@scottchiefbaker

scottchiefbaker commented Apr 16, 2026

Copy link
Copy Markdown
Contributor Author

This script outputs the average number of bits of randomness seen in rand()

use strict;
use warnings;
use v5.16;
use List::Util qw( sum );

sub avg { sum(@_)/@_ }

###############################################################################

my @data;
for (1 .. 1000) {
    my $str = sprintf("%064b", int(rand() * 2**64 - 1));

    my ($zeros) = $str =~ m/(0*)$/;
    push(@data, 64 - length($zeros) + 1);
}

printf("~%0.2f bits\n", avg(@data));

Prior to this patch, I consistently get ~48 bits of randomness. After landing this we get the full 53 bits of entropy a double can hold. Getting more bits of entropy is nice, but we also get much better statistical properties from a new PRNG as well.

@scottchiefbaker

Copy link
Copy Markdown
Contributor Author

Using Smokerand we can see that PCG64 is significantly faster than drand48.

# drand48
Nanoseconds per call:
  Raw result:                 2.44578
  For empty 'dummy' PRNG:     1.08109
  Corrected result:           1.36469
  Corrected result (GiB/sec): 2.72977

# PCG64  
Nanoseconds per call:
  Raw result:                 1.94734
  For empty 'dummy' PRNG:     1.08104
  Corrected result:           0.866292
  Corrected result (GiB/sec): 8.60054

This change should give us a boost in rand() generations per second.

@tonycoz

tonycoz commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Using Smokerand we can see that PCG64 is significantly faster than drand48.

I'm surprised by that - perl's implementation is just a multiplication and an add on 64-bit systems, while PCG64 is 2 multiplies and several other ops.

Did you test perl's drand48() or the system drand48()?

@scottchiefbaker

scottchiefbaker commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

The speed comparison results are direct from Smokerand comparing the two algorithms. I was surprised also.

smokerand speed bin/generators/drand48.so

vs

smokerand speed bin/generators/pcg64_64.so

@scottchiefbaker
scottchiefbaker force-pushed the modern_rand branch 2 times, most recently from 237fc33 to c145c04 Compare June 1, 2026 20:49
@scottchiefbaker
scottchiefbaker force-pushed the modern_rand branch 2 times, most recently from 9f10ad7 to 12c44a4 Compare July 15, 2026 21:46
@jkeenan jkeenan removed the defer-next-dev This PR should not be merged yet, but await the next development cycle label Jul 15, 2026
Comment thread perl.c Outdated
scottchiefbaker and others added 15 commits August 5, 2026 18:14
since we may not be using drand48, just something that returns
double (at least for now)
Some of this will be further changed:

- moved the pcg64_random_t type to util.h so it's visible to intrpvar.h
- changed the PL_RANDOM_STATE_TYPE to pcg64_random_t
- changed the internal RNG to reference the new RNG and changed the
  macro names to something more generic (without "drand48")
- added pcg64_random_t * parameters to the seed and RNG generation
  functions (and rename with _r) so we can make them thread safe and
  eliminate the global non-thread-safe random state
- Needed to move the functions out of prng.h so porting/args_assert
  could find the function definitions and eliminate prng.h
- re-define the original non-thread-safe functions as macro wrappers
  that pass in the state to the _r versions of the functions

I plan to move all of the random stuff out of util.h/util.c into
perlrand.h and random.c.
Perl supports larger floats, the existing code would drop bits of
randomness on the floor, avoid that.
Make it easier to find the bits of perl that deal with random number
generation and seeding.
This is an old artifact and just needs to be cleaned up
Comment thread random.c
#include <sys/random.h>
#endif

// https://prng.di.unimi.it/#remarks

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

perlhack disallows C++ (//) style comments (I don't know why)

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

Successfully merging this pull request may close these issues.

6 participants